当前位置: 首页 > 知识库问答 >
问题:

使用lambda创建std::函数会导致lambda对象的多余复制-为什么?

呼延高超
2023-03-14
#include <iostream>
#include <functional>

// Testing class - just to see constructing/destructing.
class T {
private:
    static int idCounter; // The global counter of the constructed objects of this type.
public:
    const int id; // Unique object ID 

    inline T() : id(++idCounter) { 
        std::cout << "  Constuctor Id=" << id << std::endl;
    };
    inline T(const T& src) : id(++idCounter) {
        std::cout << "  Copy constructor Id=" << id << std::endl;
    }
    inline T(const T&& src) : id(++idCounter) {
        std::cout << "  Move constructor Id=" << id  << std::endl;
    }
    inline void print() const {
        std::cout << "  Print is called for object with id=" << id << std::endl;
    }
    inline ~T() {
        std::cout << "  Destructor Id=" << id << std::endl;
    }
};

int T::idCounter=0; 

// Declare type of the std::function to store our lambda.
typedef std::function<int (void)> Callback;

int main()
{ 
    std::cout << "Let's the game begin!" << std::endl;
    T obj; // Custruct the first object.
    std::cout << "Let's create a pointer to the lambda." << std::endl;
    // Make a labmda with captured object. (The labmda prints and returns object's id).
    // It should make one (local) copy of the captured object but it makes it twice - why?!
    const Callback* pcb= new Callback( [obj]() -> int { 
        obj.print();
        return obj.id; 
    } );
    std::cout << "Now let's print lambda execution result."  << std::endl;
    std::cout << "The functor's id is " << (*pcb)() << std::endl;
    std::cout << "Destroying the lambda." << std::endl;
    delete pcb;
    std::cout << "Terminating." << std::endl;
    return 0;

}

输出为:

  Let's the game begin!
   Constuctor Id=1
  Let's create a pointer to the lambda.
   Copy constructor Id=2
   Move constructor Id=3
  Destructor Id=2
   Now let's print lambda execution result.
   Print is called for object with id=3
   The functor's id is 3
  Destroying the lambda.
   Destructor Id=3
  Terminating.
   Destructor Id=1
  

我用带有捕获对象lambda创建了一个std:function。它应该为lambda创建对象的本地副本,但它创建了两次副本(看看move constructor call-用粗体突出显示)。实际上,它会复制整个lambda对象。为什么?我怎么才能避免呢?我正在使用lambdas进行线程间事件处理,它们可能会捕获大量的日期,所以我试图找到一种方法来避免不必要的复制。因此,任务很简单--以最小的开销将构造的lambda传递到函数中--如果它将为每个构造的lambda复制两次数据,我将寻找另一种处理事件的方法。
我使用的是GCCV4.7.2强制到GNU C++11。

共有1个答案

贺刚毅
2023-03-14

好吧,输出很混乱,因为编译器执行了一个copy-elision。因此,为了理解这种行为,我们需要暂时禁用copy-elision。编译代码时使用-fno-elide-constructors标志:

$ g++ -std=c++11 -fno-elide-constructors main.cpp

现在它给出了这个输出(demo-withon-copy-elision):

Let's create a pointer to the lambda.
  Copy constructor Id=2
  Move constructor Id=3
  Move constructor Id=4
  Destructor Id=3
  Destructor Id=2

嗯,这是意料之中的。复制是在创建lambda时完成的:

 [obj]() -> int { 

//^^^^ COPY!

    obj.print();
    return obj.id; 
}

第一个移动是在将lambda传递给std::function的构造函数时完成的,因为lambda是一个rvalue,因此调用move-constructor。请注意,-fno-elide-constructors还禁用了move-elision(毕竟这只是copy的一个更快的版本!)。

第二步是在写入构造函数初始化列表中的std::function的成员数据时完成的。

到目前为止还不错。

现在,如果删除-fno-elide-constructors,编译器将优化第一个移动(因此它不调用移动构造函数),这就是为什么输出如下:

Let's create a pointer to the lambda.
  Copy constructor Id=2
  Move constructor Id=3
  Destructor Id=2

请参阅demo-with-copy-elision。

您现在看到的移动是因为将-the-lambda移动到std::function的成员数据中。你不能回避这一步。

还要注意,复制/移动lambda也会导致复制/移动捕获的数据(即递归复制/移动)。

无论如何,如果您担心复制捕获的对象(假设它是一个巨大的对象),那么我建议您使用new创建捕获的对象,这样复制捕获的对象就意味着复制一个指针(4或8字节!)。那应该很管用!

希望能有所帮助。

 类似资料:
  • 建议将以下语句更改为NetBeans中的lambda表达式。 但是,当我应用假定的更改(Alt Enter)时,我得到了: 当这不会与消息一起编译时: 没有找到合适的构造函数为KeyFrame(持续时间,(事件前夕[…]}}})-参数不匹配 问题是为什么NetBeans会提出一些它无法实现的东西?如何在Timeline构造函数中实际使用lambda函数?谢谢

  • 假设我们有我的代码的简化版本: 我试图将lambda函数作为参数传递给函数,但只有当我将lambda显式分配给特定的类型时,它才起作用。这是可行的: 上面的例子是可行的,但我想实现下面的例子,因为审美的原因,不知道这是否可能: 我唯一的要求是应该接受带有模板化返回类型的lambda和函数,以及带有特定类型的输入参数,例如。

  • 我有一个场景,需要使用AWS CDK python语言创建多个lambda函数。 当我尝试为每个函数创建多个堆栈时,它创建了第一个函数和显示该函数已经存在的其余函数。 如果我们调试cdk。对于创建的模板,两个堆栈Lambda函数显示相同的资源ID,如下所示: 如何为创建多个函数的资源获取唯一ID?你能建议如何克服这个问题吗?我想创建多个lambda函数,即使解决方案是通过创建循环来实现的,也很好

  • 我试图用Lamdas简化我的对象创建。 如下所示:

  • 为什么在添加捕获元素时,下面的lambda不符合签名? 但这会吗? 签名:

  • KeyError:我使用的“记录”Lambd@Edge使用CloudFront请求原件。我可以成功通过测试。但是,在cloudwatch中,我看到了错误: 根据文件记录,“记录”应出现在事件中。https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html