J'ai une classe C ++ qui calcule certains paramètres statistiques en déplacement. Est-il acceptable de rejeter const de this pour implémenter la mise en cache de longs calculs, si je ne change pas l'état visible publiquement, et la fonction const en question est idempotente et pure / référentiellement transparente?

#include <cstdio>
#include <unistd.h>

class Compute {
public:
    Compute() = default;
    ~Compute() = default;

    void add(int x) {
        sum += x;
        squareDirty = true;
    }

    int getSquare() const {
        if (squareDirty) {
            auto &mthis = *const_cast<Compute*>(this);
            usleep(2000); // takes a long time!
            mthis.squareCached = sum * sum;
            mthis.squareDirty = false;
        }

        return squareCached;
    }

private:
    int sum = 0;
    bool squareDirty = false;
    int squareCached;
};

void foo() {
    Compute c{};
    c.add(10);
    c.add(20);
    printf("%d\n", c.getSquare()); // long time...
    printf("%d\n", c.getSquare()); // fast!
}

Je veux calculer paresseusement les choses uniquement lorsqu'elles sont réellement nécessaires - et les mettre en cache jusqu'à ce que de nouvelles données arrivent.

Cependant, la mise en cache signifie que ma méthode T getSquare() const devrait rejeter const de this pour muter l'état privé.

Mais comme getSquare est effectivement idempotent, le compilateur peut le calculer une fois et le stocker en tant que constante, ou en ligne, ou faire quoi que ce soit d'autre, car mon état privé est supprimable.

Est-ce une chose acceptable à faire ou est-ce que je demande UB?

0
toriningen 2 juin 2020 à 20:42

2 réponses

En utilisant

auto &mthis = *const_cast<Compute*>(this);
mthis.squareCached = sum * sum;
mthis.squareDirty = false;

Peut provoquer un comportement indéfini. Cela dépend de la manière dont l'objet d'origine a été construit.

Depuis https://en.cppreference.com/w/cpp/language/const_cast:

La modification d'un objet const via un chemin d'accès non - const et la référence à un objet volatile via une valeur de glissement non - volatile entraîne un comportement indéfini.

Il vaudra mieux faire les membres concernés mutable.

int sum = 0;
mutable bool squareDirty = false;
mutable int squareCached;

Ensuite, vous pouvez utiliser:

int getSquare() const {
    if (squareDirty) {
        usleep(2000); // takes a long time!
        this->squareCached = sum * sum;
        this->squareDirty = false;
    }

    return squareCached;
}

Sans crainte d'un comportement indéfini.

1
R Sahu 2 juin 2020 à 18:07

Est-il acceptable de const_cast<Foo*>(this) si l'état public ne change pas?

Modifier l'état non mutable d'un objet const n'est pas défini.

Pour cette raison, il n'est pas acceptable de modifier l'état non mutable via une référence à const après le cast de const, sauf s'il est prouvable que l'objet référencé est non-const.

La question de savoir si cet État est public n'a aucune importance. Ce type de mise en cache transparente est la raison pour laquelle des membres mutables existent, je pense.

3
eerorika 2 juin 2020 à 17:49