Сегодня меня сильно укусило это странное поведение лямбд под XCode - после попытки отследить несколько утечек памяти в iOS вокруг кода, я сузил его до этого (и подобных) фрагментов, где я назначаю право собственности чего-то в отложенной задаче с помощью общего указателя:

void DBStorage::dispose(std::shared_ptr<DataChunk>& dc)
{
    backgroundQueue.queueTask([=]() {
        assert( dc.use_count() == 1 );

        if (dc->isDirty()) {
            //store to disk
        }
    });
}

(Обратите внимание, что счетчик использования общего указателя всегда равен 1 при запуске лямбда-выражения)

После выполнения эта задача обнуляется с помощью pendingJob = nullptr;, который, как я ожидал, вызовет деструктор всех захваченных по значению объектов и, следовательно, деструктор DataChunk. Однако похоже, что в XCode / LLVM деструктор lc никогда не вызывается; явный вызов его dtor с использованием mutable и удаление std::function с помощью простого delete также не сработали.

Это стандартное поведение? Я, конечно, могу вручную вызвать dc.reset(), и он работает, как ожидалось, но это делает вопрос использования общего указателя спорным.


Решение По-видимому, это известная ошибка gcc. .


Содействие

Автономный образец с выводом из Xcode 5.0.2 / clang 3.3

#include <iostream>
#include <memory>

void fnRef(std::shared_ptr<int>& ptr)
{
    auto lambda = [=]() { std::cout << ptr.use_count() << ':' << __PRETTY_FUNCTION__ << '\n'; };
    lambda();
}

void fnVal(std::shared_ptr<int> ptr)
{
    auto lambda = [=]() { std::cout << ptr.use_count() << ':' << __PRETTY_FUNCTION__ << '\n'; };
    lambda();
}

int main()
{
    std::shared_ptr<int> ptr(new int);
    for (int i=0; i<10; ++i)
        fnVal(ptr);
    std::cout << '\n';

    for (int i=0; i<10; ++i)
        fnRef(ptr);

    return 0;
}

Вывод LLVM / GCC

3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const
3:void fnVal(std::shared_ptr<int>)::<anonymous class>::operator()() const

2:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
3:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
4:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
5:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
6:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
7:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
8:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
9:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
10:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const
11:void fnRef(std::shared_ptr<int> &)::<anonymous class>::operator()() const

IDEOne.com Вывод для того же кода

3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
3:fnVal(std::shared_ptr<int>)::__lambda1
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0
2:fnRef(std::shared_ptr<int>&)::__lambda0

Выходные данные Visual Studio 2013

3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()
3:fnVal::<lambda_67137a3f93ee478c018cc7068004c9fd>::operator ()

2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
2:fnRef::<lambda_70f241d4201227663d23c74be170d302>::operator ()
9
Tom89 19 Фев 2014 в 22:12
1
Какую версию компилятора вы используете? В GCC была ошибка, из-за которой ссылка была записана по значению. stackoverflow.com/ questions / 6529177 /…
 – 
Dave S
19 Фев 2014 в 22:22
clang --version говорит: Apple LLVM version 5.0 (clang-500.2.75) (based on LLVM 3.3svn) - это определенно выглядит возможным, однако сначала копирование dc в локальной области, а затем захват копии тоже не сработали!
 – 
Tom89
19 Фев 2014 в 22:31
Том, ты не видишь вещей. У меня просто была такая же проблема с написанием примера кода для этого, и теперь я не могу воспроизвести его всю жизнь. Когда я говорю "просто", я имею в виду 5 минут назад. Я изменил местоположение std::cout, и теперь его больше не происходит, и его изменение назад не имеет значения. И нет, я не выдумываю. Я посмотрю, я смогу сделать это снова. (и я использую тот же Xcode / clang, что и вы, кстати).
 – 
WhozCraig
19 Фев 2014 в 22:32
Я отключился при попытке скопировать в локальную область видимости ... auto local = lc не скопирует указатель! Код, предложенный @Yakk, работает :)
 – 
Tom89
19 Фев 2014 в 22:40
Может быть, но вы можете найти вывод, который я внес в ваш вопрос, интересным. Я, конечно, сделал.
 – 
WhozCraig
19 Фев 2014 в 22:45

1 ответ

Лучший ответ

Как отметил @DaveS, это может быть известная ошибка gcc - захваченные ссылки сохраняются как ссылки.

Хорошее практическое правило при работе с сохраненными лямбдами - избегать =, так как с сохраненным состоянием следует обращаться осторожно.

void DBStorage::dispose(std::shared_ptr<DataChunk>& dc)
{
  std::shared_ptr<DataChunk> data_to_store = dc;
  backgroundQueue.queueTask([data_to_store]() { // maybe add `,this` to the capture list
    assert( data_to_store.use_count() == 1 );
    if (data_to_store->isDirty()) {
      //store to disk
    }
  });
}

Или же:

void DBStorage::dispose(std::shared_ptr<DataChunk> data_to_store)
{
  backgroundQueue.queueTask([data_to_store]() { // maybe add `,this` to the capture list
    assert( data_to_store.use_count() == 1 );
    if (data_to_store->isDirty()) {
      //store to disk
    }
  });
}

В качестве второй части незапрошенного совета, std::function не являются лямбдами, и вызов одного theLambda вводит в заблуждение.

5
Community 23 Май 2017 в 15:28
Спасибо, я ничего не заметил, когда попробовал это самостоятельно, когда написал auto data_to_store = dc, получив еще одну ссылку! Кроме того, theLambda на самом деле называется pendingJob в реальном коде, но я отредактирую OP, чтобы он не вводил в заблуждение :)
 – 
Tom89
19 Фев 2014 в 22:42
И ... как так известная ошибка GCC применима как есть к совершенно другому компилятору? Это довольно странно и похоже на разногласия по спецификации?
 – 
Tom89
19 Фев 2014 в 22:45
1
Подождите, auto data_to_store = dc; должно быть значением - auto выводится как параметр T. /головоломка
 – 
Yakk - Adam Nevraumont
19 Фев 2014 в 22:59
Я снова поправляюсь, auto data_to_store действительно работает. Никогда не верьте «доказательствам», которые вы помните из многочасовых поисков ошибок! Возможно, что-то еще сломалось, когда я тогда попробовал, кто знает.
 – 
Tom89
19 Фев 2014 в 23:11
2
[&] только при создании лямбды, которая умирает (со всеми копиями) до конца текущей области видимости. [vars,explicit] при создании постоянной лямбды. [=] при написании одноразового кода или в процессе рефакторинга.
 – 
Yakk - Adam Nevraumont
20 Фев 2014 в 00:45