Visual C++: MSVC vs. GCC+CLANG: 处理 lambda 捕获类成员变量,正确的方法是什么?

visual c++: MSVC vs. GCC+CLANG: Handling lambdas capturing class member variables, what is the correct approach?

本文关键字:变量 方法 是什么 成员 vs MSVC C++ GCC+CLANG 处理 Visual lambda      更新时间:2023-10-16

考虑以下看起来非常无辜的代码段:

#include <functional>
#include <iostream>
#include <list>
#include <memory>
struct foo;
std::list<std::weak_ptr<foo>> wptrs;
std::function<void()> global_func;
struct foo {
int &a;
foo(int &a) : a{ a } {  }
~foo() {
global_func = [&] {
wptrs.remove_if([](auto &x) { return x.expired(); });
std::cout << "a= " << a << std::endl;
};
}
};
int main() {
int a = 5;
auto ptr = std::make_shared<foo>(a);
wptrs.emplace_back(ptr);
ptr = nullptr;  // object is destroyed here
global_func();
return 0;
}

当我第一次在MSVC(Visual Studio 2017(上遇到这个问题时,我正在TCP/IP服务器上工作,该服务器试图清理连接对象的weak_ptr列表。连接对象调度 lambda 以通过调用weak_ptr<T>::expired()来清除weak_ptr连接的列表。我之前很高兴,因为使用 clang-6.0+ 或 g++-7+ 编译时一切都可以正常工作。然后,我不得不使用 MSVC,并在调用析构时收到读取访问冲突。我很震惊,并试图生成一个显示相同问题的最小示例。上面给出了最小的例子。

最小的示例使错误消息清晰,似乎MSVC lambda试图访问this->__this->a。此访问序列表明 MSVC 不捕获a的地址(或对a的引用(,而是捕获*this的地址并使用此对象获取对a的访问权限。由于当弱引用计数变为零时,*this对象被完全释放,因此我遇到了内存访问错误。

显然,MSVC方法与g++和clang的方法完全不同。所以,我的问题是哪个编译器是正确的?

附言MSVC 案例的简单修复:

#include <functional>
#include <iostream>
#include <list>
#include <memory>
struct foo;
std::list<std::weak_ptr<foo>> wptrs;
std::function<void()> global_func;
struct foo {
int &a;
foo(int &a) : a{ a } {  }
~foo() {
global_func = [a = &a] {    // capture a ptr instead
wptrs.remove_if([](auto &x) { return x.expired(); });
std::cout << "a= " << *a << std::endl;
};
}
};
int main() {
int a = 5;
auto ptr = std::make_shared<foo>(a);
wptrs.emplace_back(ptr);
ptr = nullptr;  // object is destroyed here
global_func();
return 0;
}

*this的成员永远不会被捕获:它们不能被显式捕获,并且隐式使用它们会捕获*this(通过引用而不考虑捕获默认值;为清楚起见,请考虑使用[=,this](。 所以你的第二个例子是唯一正确的方法;GCC 和 Clang 可能一直在优化foo::a的使用,因为引用无法反弹。