c ++: MSVC vs. GCC + CLANG: Как правильно использовать лямбда-выражения, фиксирующие переменные членов класса? - PullRequest
0 голосов
/ 08 октября 2018

Рассмотрим следующий сегмент кода, который выглядит очень невинно:

#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 s для объектов подключения.Объект соединения планирует лямбду для очистки списка weak_ptr с соединений, вызывая weak_ptr<T>::expired().Я был счастлив раньше, потому что все работало нормально при компиляции с clang-6.0+ или g ++ - 7+.Затем я должен был использовать MSVC и получил нарушение прав на чтение при вызове destruct.Я был шокирован и попытался создать минимальный пример, демонстрирующий ту же проблему.Минимальный пример приведен выше.

Минимальный пример прояснил сообщения об ошибках, и казалось, что MSVC lambda пытается получить доступ к this->__this->a.Эта последовательность доступа предполагает, что MSVC не захватывает адрес a (или ссылку на a), а скорее захватывает адрес *this и получает доступ к a, используя этот объект.Поскольку объект *this полностью освобождается, когда слабый счетчик ссылок становится равным нулю, у меня возникает ошибка доступа к памяти.

Очевидно, что подход MSVC радикально отличается от подхода g ++ и clang.Итак, мой вопрос, какой компилятор подходит?

PS Простое исправление для случая 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;
}

1 Ответ

0 голосов
/ 09 октября 2018

Члены *this никогда не перехвачены: они не могут быть явно перехвачены , а их использование неявно перехватывает *this (по ссылке независимо от того, захват-по умолчанию ; рассмотрите возможность использования [=,this] для ясности).Таким образом, ваш второй пример - единственный правильный способ сделать это;Возможно, GCC и Clang оптимизировали использование foo::a, поскольку ссылки не могут быть восстановлены.

...