通过 lambda 调用成员函数时意外未初始化的数据

Unexpectedly uninitialised data in call to member function through lambda

本文关键字:初始化 数据 意外 lambda 调用 成员 函数 通过      更新时间:2023-10-16

考虑下面的简短程序。我正在使用std::function和lambda将成员函数对象(initialise)设置为同一类(A)中的成员函数(initialiser)。 然后initialise()在 4 个地方被调用:1. 在来自mainA的构造函数中,2. 直接从main调用,3. 在A的构造函数中来自B的构造函数中,以及 4. 直接从B的构造函数中调用。

#include <iostream>
#include <functional>
#include <vector>
class A {
public:
A(std::vector<int> const& sizes) : m_sizes(sizes) {
set_initialiser();
initialise();
}
A() = default;
std::function<void()> initialise;
void set_initialiser() { initialise = [this]() {return initialiser(); }; };
// void set_initialiser() { initialise = std::bind(&A::initialiser, this); }; // the same occurs if I use bind instead of a lambda
private:
std::vector<int> m_sizes;
void initialiser() {
std::cout << "m_sizes size = " << m_sizes.size() << ", with contents:";
for (auto & s : m_sizes)
std::cout << " " << s;
std::cout << std::endl;
};
};
class B {
public:
B(std::vector<int> const& v) {
a = A(v);
a.initialise(); // here a.m_sizes and a.initialise.functor.this.m_sizes differ
};
private:
A a;
};
int main(int argc, char* argv[])
{
auto a = A({ 4,3,2,1 });
a.initialise();
auto b = B({ 4,3,2,1 });
return 0;
}

编译和运行此代码会产生以下意外(至少对我来说)行为。

m_sizes size = 4, with contents: 4 3 2 1                                                                                
m_sizes size = 4, with contents: 4 3 2 1                                                                                            
m_sizes size = 4, with contents: 4 3 2 1                                                                                
m_sizes size = 0, with contents:

谁能阐明为什么最后一次调用initialise()包含未初始化的m_sizes?我怀疑它一定与 lambda 中使用的不同this实例有关,但我不明白为什么从main调用它和从另一个类调用它应该有任何区别。

a = A(v);

此行创建一个临时对象A(v)(我称之为t),并将其数据复制到a中。t.initialise包含一个按值捕获this(即&t)的 lambda。然后,将此 lambda 复制到a.initialise中;但是请注意,它仍然指的是&t.

然后,完整的表达式结束,t被销毁(因为它是临时的),并且捕获的指针现在悬空a.initialise。因此,下一次调用a.initialise()会取消引用一个悬空的指针,从而为您提供未定义的行为。

请注意,完全相同的问题(相同的未定义行为)也发生在您的main中,但其效果不同。以下是对原因的猜测,但请记住,未定义的行为是未定义的,任何事情都可能发生。

我假设在main内部,复制省略发生,临时A({ 4,3,2,1 })直接在a空间中构造,使其thisa相同,这使得对initialise的调用仍然有效。

B的构造函数中,复制 elision 是不可能的(因为你是在做赋值而不是初始化),所以临时确实被销毁了,留下了一个正确销毁的向量,它恰好仍然与你情况下的空向量相同(可能是因为它从临时向量移动到a)。

再说一遍:这只是猜测,代码是完全错误的。