如果 public 状态不变,`const_cast<Foo*>(this)` 是否可以接受?

Is it acceptable to `const_cast<Foo*>(this)` if public state doesn't change?

我有一个 C++ class 可以随时计算一些统计参数。如果我不更改公开可见的状态,并且所讨论的 const 函数是幂等的并且 pure/referentially 透明,是否可以从 this 中抛弃 const 以实现冗长计算的缓存?

#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!
}

我只想在实际需要时才懒惰地计算事物 — 并缓存它们直到新数据到达。

然而,缓存 事情意味着我的 T getSquare() const 方法必须从 this 中抛弃 const 以改变私有状态。

但由于 getSquare 实际上是幂等的,编译器可以计算一次并将其存储为常量、内联或做任何其他事情,因为我的私有状态是可丢弃的。

这是可以接受的事情,还是我要求 UB?

此问题已通过使用 mutable 关键字解决。该关键字表示可以在const限定的函数中修改成员变量:

struct S
{
  mutable int x;  // some data that is not relevant to users of the object (e.g. a cache variable)
  int y;

  void f() const 
  {
     x = 42;  // fine 
     y = 42;  // error
  }
};

与放弃 const 正确性或使用 const_cast(这是 UB 的潜在来源)相比,这是一种在 const 限定函数中修改变量的更好方法。

请注意,当您创建一个函数 const 时,您实际上保证的不仅仅是 "the visible state is not changed"。您保证通过任何 const 函数访问对象是线程安全的。这意味着如果你有 mutable 个变量,你应该仔细考虑它们的状态,如果多个线程可能同时访问它们。在这种情况下,您可能应该考虑自己手动同步对该变量的访问。

Is it acceptable to const_cast<Foo*>(this) if public state doesn't change?

修改 const 对象的非可变状态是未定义的行为。

因此,在 const 转换之后通过对 const 的引用来修改非可变状态是不可接受的,除非可以证明所引用的对象是非常量。

该状态是否为 public 无关紧要。我认为这种类型的透明缓存是可变成员存在的原因。

使用

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

可能会导致未定义的行为。这取决于原始对象的构造方式。

来自https://en.cppreference.com/w/cpp/language/const_cast

Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior.

做相关会员会更好mutable.

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

那么,您可以使用:

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

    return squareCached;
}

不用担心未定义的行为。