简体   繁体   中英

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

Consider the following code segment which looks very innocent:

#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;
}

When I first encountered the problem on MSVC (Visual Studio 2017), I was working on a TCP/IP server, which tries to clean up a list of weak_ptr s to connection objects. The connection object schedules a lambda to clear a list of weak_ptr s of connections by calling weak_ptr<T>::expired() . I was happy before because everything used to work fine when compiled with clang-6.0+ or g++-7+. Then, I had to use MSVC and received a Read Access violation when destruct was called. I was shocked and tried to generate a minimal example exhibiting the same problem. The minimal example is given above.

The minimal example made the error messages clear, and it seemed like MSVC lambda tries to access to this->__this->a . This access sequence suggests that MSVC does not capture the address of a (or a reference to a ), but rather it captures the address of *this and acquires access to a using this object. Since the *this object is completely deallocated when weak ref count becomes zero, I have a memory access error.

Obviously, MSVC approach is radically different than the approach of g++ and clang. So, my question is which compiler is right?

PS A simple fix for MSVC case:

#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;
}

Members of *this are never captured: they can't be explicitly captured , and using them implicitly captures *this (by reference regardless of the capture-default ; consider using [=,this] for clarity). So your second example is the only correct way to do it; GCC and Clang may have been optimizing away the use of foo::a since references can't be rebound.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM