我有一个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?
答案 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;
}
不用担心不确定的行为。