我正在测试惰性求值的实现。为此,我将一个 lambda 函数作为参数传递给成员构造函数,该函数仅在第一次评估发生时执行。但是,我注意到 lambda 函数可以通过捕获的变量访问类成员变量,并且我可以在 lambda 函数内部修改它们。
我的 const 限制测试函数如下:
void print_root(const Foo& f) {
for(auto i:iota(0,10)) std::cout << f.root() << std::endl;
}
实现如下
template<typename T>
class Lazy {
private:
std::function<T()> get_value;
mutable std::mutex mutex;
mutable bool evaluated = false;
mutable T value;
public:
Lazy(std::function<T()> f) : get_value(f) {}
const T& operator()() const {
std::lock_guard<std::mutex> lock(mutex);
if(!evaluated) {
value = get_value();
evaluated = true;
}
return value;
}
};
struct Foo {
private:
double x;
public:
Lazy<double> root;
Foo(double _x) : x(_x), root(
[this]()->double {
double a = x/2;
std::cout << "Calculating root of " << x << std::endl;
for(auto i:iota(0, 1000000000)) a = (a + x/a) / 2;
x = 3; //try to modify this-> x
return a;
}
) {}
};
然后 g++ 编译器不报告任何错误。
捕获
this
的行为本质上就好像您已经捕获了具有相同类型的 this
的指针值,并且 lambda 主体中的每个成员访问都被重写为通过该捕获的指针进行访问。
在
root
的默认成员初始化器中,this
的类型是Foo*
,而不是const Foo*
。所以捕获的this
也将是非const
。那么 lambda 的 operator()
是否符合 const
并不重要。如果你有一个 Foo* const
类型的指针,那么取消引用它仍然会给你一个 Foo
左值,而不是 const Foo
左值。
这与将指针存储为类成员完全相同:
struct Foo {
int a = 0;
Foo* x = this;
void f() const {
this->a++; // error, `this` is prvalue of type `const Foo*`
// so `*this` is lvalue of type `const Foo`
x->a++; // ok, `x` is lvalue of type `Foo* const`,
// after lvalue-to-rvalue conversion of type `Foo*`
// so `*x` is lvalue of type `Foo`
}
};
(还请考虑我和其他人在有关设计和使用
Lazy
的一般问题的问题下发表的评论。)