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

时间:2020-06-02 17:42:45

标签: c++ constants c++20

我有一个C ++类,可以随时随地计算一些统计参数。如果我不更改公开可见的状态,并且有问题的this函数是幂等且纯净/参照透明的,则从const删除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方法必须从const丢弃this才能改变私有状态。

但是由于getSquare实际上是幂等的,因此编译器可以对其进行一次计算,并存储为常量,内联或其他任何方式,因为我的私有状态是可丢弃的。

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

3 个答案:

答案 0 :(得分:3)

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

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

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

国家是否公开无关紧要。我认为,这种类型的透明缓存是存在可变成员的原因。

答案 1 :(得分:1)

使用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时,实际上可以保证的不仅仅是“可见状态不变”。您保证通过任何const函数访问对象都是线程安全的。这意味着,如果您拥有mutable变量,则如果多个线程可以同时访问它们,则应仔细考虑它们的状态。在这种情况下,您可能应该考虑自己手动同步对此变量的访问。

答案 2 :(得分:1)

使用

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

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

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

通过非const访问路径修改const对象,并通过非volatile glvalue引用volatile对象会导致不确定的行为。

最好让相关成员成为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;
}

不用担心不确定的行为。

相关问题