【问题标题】:C++ lambda won't call the destructor on members captured by valueC++ lambda 不会在值捕获的成员上调用析构函数
【发布时间】:2014-03-20 06:42:06
【问题描述】:

今天,我被 XCode 下 lambdas 的这种奇怪行为严重咬伤了——在尝试跟踪 iOS 中围绕代码的几个内存泄漏之后,我将其范围缩小到这个(和类似的)sn-p(s)我使用共享指针将某物的所有权分配给延迟任务:

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

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

(请注意,当 lambda 运行时,共享指针的使用计数始终为 1)

在执行之后,这个任务被 pendingJob = nullptr; 置空,我希望它调用所有按值捕获的对象的析构函数,从而调用 DataChunk 的析构函数。 但是,看起来在 XCode/LLVM lc 的析构函数下从未调用过;使用mutable 显式调用其dtor,并使用简单的delete 删除std::function 也不起作用。

这是标准行为吗? 我当然可以手动调用dc.reset(),它可以按预期工作,但这很说明使用共享指针没有实际意义。


解决方案 显然是known gcc bug


贡献

带有 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 Output for same code

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 ()

【问题讨论】:

  • 您使用的是什么版本的编译器? GCC 中有一个错误,即引用的按值捕获是引用。 stackoverflow.com/questions/6529177/…
  • clang --version 说,Apple LLVM version 5.0 (clang-500.2.75) (based on LLVM 3.3svn) - 这看起来确实可行,但是首先在本地范围内复制 dc 然后捕获副本也不起作用!
  • Tom,你没有看到我只是在为此编写示例代码时遇到了同样的问题,现在我无法终生重现它.当我说“只是”时,我的意思是 5 分钟前。我更改了std::cout 位置,现在它不再发生,更改它 back 没关系。不,我不是在编造这个。我会看到我可以再做一次。 (顺便说一句,我正在运行与您相同的 Xcode/clang)。
  • 我在尝试复制到本地范围时失败了...auto local = lc 不会复制指针! @Yakk 建议的代码有效:)
  • @Tom89 也许是这样,但您可能会发现我为您的问题提供的输出很有趣。我当然做到了。

标签: c++ xcode c++11 lambda llvm


【解决方案1】:

正如@DaveS 所指出的,这可能是known gcc bug——捕获的引用被存储为引用。

使用存储的 lambda 时,一个好的经验法则是避免使用 =,因为应小心处理存储状态。

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::functions 不是 lambda,调用 theLambda 具有误导性。

【讨论】:

  • 谢谢,当我写auto data_to_store = dc 得到另一个参考时,我自己尝试这个时没有发现!此外,theLambda 在实际代码中实际上称为pendingJob,但我将编辑 OP 以减少误导:)
  • 而且...一个已知的 GCC 错误如何按原样应用于完全不同的编译器?这很奇怪,而且看起来在规范上存在分歧?
  • @Tom89 等等,auto data_to_store = dc; 应该是一个值——auto 推导类似于 T 参数。 /谜题
  • 我再次纠正,auto data_to_store 确实有效。永远不要相信你从几个小时的错误搜寻中记住的“证据”!我当时尝试的时候可能有其他东西坏了,谁知道呢。
  • @Casey [&amp;] 仅在创建一个在当前范围结束之前死亡(包含所有副本)的 lambda 时。 [vars,explicit] 创建持久 lambda 时。 [=] 在编写一次性代码或重构过程中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-29
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 2017-05-03
相关资源
最近更新 更多