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 的使用,因为引用不能被反弹。