c++: MSVC vs. GCC+CLANG: 处理 lambdas 捕获 class 成员变量,什么是正确的方法?
c++: MSVC vs. GCC+CLANG: Handling lambdas capturing class member variables, what is the correct approach?
考虑以下看起来非常无辜的代码段:
#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,并在调用 destruct 时收到读取访问冲突。我很震惊,并试图生成一个展示相同问题的最小示例。上面给出了最小的例子。
最小示例使错误消息清晰明了,似乎 MSVC lambda 试图访问 this->__this->a
。此访问序列表明 MSVC 不捕获 a
的地址(或对 a
的引用),而是捕获 *this
的地址并获取对 a
的访问权使用这个对象。由于当弱引用计数变为零时 *this
对象被完全释放,我有一个内存访问错误。
显然,MSVC 方法与 g++ 和 clang 的方法完全不同。所以,我的问题是哪个编译器是正确的?
P.S。 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
的成员从未 被捕获:他们can’t be explicitly captured, and using them implicitly captures *this
(通过参考,无论 捕获默认值;为清楚起见,请考虑使用 [=,this]
)。所以你的第二个例子是唯一正确的方法; GCC 和 Clang 可能一直在优化 foo::a
的使用,因为引用不能被反弹。
考虑以下看起来非常无辜的代码段:
#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,并在调用 destruct 时收到读取访问冲突。我很震惊,并试图生成一个展示相同问题的最小示例。上面给出了最小的例子。
最小示例使错误消息清晰明了,似乎 MSVC lambda 试图访问 this->__this->a
。此访问序列表明 MSVC 不捕获 a
的地址(或对 a
的引用),而是捕获 *this
的地址并获取对 a
的访问权使用这个对象。由于当弱引用计数变为零时 *this
对象被完全释放,我有一个内存访问错误。
显然,MSVC 方法与 g++ 和 clang 的方法完全不同。所以,我的问题是哪个编译器是正确的?
P.S。 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
的成员从未 被捕获:他们can’t be explicitly captured, and using them implicitly captures *this
(通过参考,无论 捕获默认值;为清楚起见,请考虑使用 [=,this]
)。所以你的第二个例子是唯一正确的方法; GCC 和 Clang 可能一直在优化 foo::a
的使用,因为引用不能被反弹。