如何使C++;lambda对象更高效? 最近我一直在考虑存储C++ lambda。您在Internet上看到的标准建议是将lambda存储在std::function对象中。但是,这些建议都没有考虑到存储的影响。我突然想到,一定有一些严重的巫毒正在幕后进行,使这项工作。考虑下面的类,它存储一个整数值: class Simple { public: Simple( int value ) { puts( "Constructing simple!" ); this->value = value; } Simple( const Simple& rhs ) { puts( "Copying simple!" ); this->value = rhs.value; } Simple( Simple&& rhs ) { puts( "Moving simple!" ); this->value = rhs.value; } ~Simple() { puts( "Destroying simple!" ); } int Get() const { return this->value; } private: int value; }; 现在,考虑这个简单的程序: int main() { Simple test( 5 ); std::function<int ()> f = [test] () { return test.Get(); }; printf( "%d\n", f() ); }

如何使C++;lambda对象更高效? 最近我一直在考虑存储C++ lambda。您在Internet上看到的标准建议是将lambda存储在std::function对象中。但是,这些建议都没有考虑到存储的影响。我突然想到,一定有一些严重的巫毒正在幕后进行,使这项工作。考虑下面的类,它存储一个整数值: class Simple { public: Simple( int value ) { puts( "Constructing simple!" ); this->value = value; } Simple( const Simple& rhs ) { puts( "Copying simple!" ); this->value = rhs.value; } Simple( Simple&& rhs ) { puts( "Moving simple!" ); this->value = rhs.value; } ~Simple() { puts( "Destroying simple!" ); } int Get() const { return this->value; } private: int value; }; 现在,考虑这个简单的程序: int main() { Simple test( 5 ); std::function<int ()> f = [test] () { return test.Get(); }; printf( "%d\n", f() ); },c++,c++11,C++,C++11,首先,我们创建值测试。我们在堆栈上为临时lambda对象创建一个本地副本。然后,我们将临时lambda对象移动到std::function分配的内存中。我们摧毁了临时的lambda。我们打印输出。我们破坏std::函数。最后,我们销毁测试对象 不用说,这不是我所看到的。当我在Visual C++ 2010(发布或调试模式)编译时,我得到这个输出: Constructing simple! Copying simple! Copying simple! Copying simple! Copyin

首先,我们创建值测试。我们在堆栈上为临时lambda对象创建一个本地副本。然后,我们将临时lambda对象移动到std::function分配的内存中。我们摧毁了临时的lambda。我们打印输出。我们破坏std::函数。最后,我们销毁测试对象

不用说,这不是我所看到的。当我在Visual C++ 2010(发布或调试模式)编译时,我得到这个输出:

Constructing simple!
Copying simple!
Copying simple!
Copying simple!
Copying simple!
Destroying simple!
Destroying simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
天哪,这太没效率了!编译器不仅没有使用我的move构造函数,而且在赋值过程中生成并销毁了两个明显多余的lambda副本


所以,这里最后是问题:(1)所有这些复制真的有必要吗?(2) 有没有办法强迫编译器生成更好的代码?谢谢你的阅读

问题在于std::function不使用移动语义,在初始化时复制lambda。这是一个糟糕的实现

这里有一个小技巧,你可以用来绕过这个问题

template<typename T>
class move_lambda
{
    T func_;

public:
    move_lambda(T&& func) : func_(std::move(func)){}            
    move_lambda(const move_lambda& other) : func_(std::move(other.func_)){} // move on copy 
    auto operator()() -> decltype(static_cast<T>(0)()){return func_();}
};

template <typename T>
move_lambda<T> make_move_lambda(T&& func)
{
    return move_lambda<T>(std::move(func));
}
模板
类移动_lambda
{
T函数;
公众:
move_lambda(T&&func):func_(std::move(func)){
move_lambda(const move_lambda&other):func_(std::move(other.func_)){}//复制时移动
自动运算符()()->decltype(static_cast(0)(){return func_();}
};
模板
移动lambda使移动lambda(T&&func)
{
返回move_lambda(std::move(func));
}
用法:

int main()
{
    Simple test( 5 );

    std::function<int ()> f(make_move_lambda(
        [test] ()
        {
            return test.Get();
        }));

    printf( "%d\n", f() );
}
intmain()
{
简单试验(5);
函数f(使移动)(
[测试]()
{
returntest.Get();
}));
printf(“%d\n”,f());
}
您在Internet上看到的标准建议是将lambda存储在std::function对象中。但是,这些建议都没有考虑到存储的影响

那是因为没关系。您无权访问lambda的类型名。因此,虽然最初可以使用
auto
将其存储在其本机类型中,但它不会将该范围保留为该类型。您不能将其作为该类型返回。你只能把它粘在别的东西上。C++11提供的唯一“其他东西”是
std::function

因此,您有一个选择:使用
auto
暂时保留它,并锁定在该范围内。或者将其粘贴在
std::function
中进行长期存储

所有这些复制真的有必要吗

技术上?不,它不是
std::function
所必需的

有没有办法强迫编译器生成更好的代码


不,这不是你的编译器的错;这就是
std::function
的特定实现的工作原理。它可以减少复制;它不必复制两次以上(并且取决于编译器生成lambda的方式,可能只有一次)。但确实如此。

您的第一个问题就是MSVC对
std::function
的实现效率低下

不过,这仍然在创建一个额外的副本。问题是lambda正在按值捕获
test
,这就是您拥有所有副本的原因。尝试:

int main()
{
    Simple test( 5 );

    std::function<int ()> f =
        [&test] ()               // <-- Note added &
        {
            return test.Get();
        };

    printf( "%d\n", f() );
}

请注意,如果通过引用进行捕获,则必须确保
test
f
的生命周期内保持活动状态,否则将使用对已销毁对象的引用,这会引发未定义的行为。如果
f
需要比
test
更长寿,则必须使用传递值版本。

我不久前注意到MSVC10存在相同的性能问题,并在microsoft connect上提交了一份错误报告:

错误关闭为“已修复”。 使用MSVC11 developer预览您的代码并打印:

Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!

使用C++14,您可以完全避免复制:

int main(){
    Simple test( 5 );
    std::function<int ()> f =[test = std::move(test)](){
        return test.Get();
    };
    printf( "%d\n", f() );
}
请注意以下行:

[test = std::move(test)] 

这里,第一次出现的“test”与第二次出现的范围不同。

GCC给出了您所期望的复制/移动/销毁序列。很高兴知道这一点。我想这意味着(1)的答案是否定的。希望未来的MS实现会更好。如果您添加移动分配操作符会怎么样?我认为,将VS2010中的C++11功能视为低效是不公平的;各自的日期应该是一个原因提示。如果要存储lambda,通过引用捕获是一个非常糟糕的主意。只要
test
仍处于活动状态(在本例中就是这样),那么就可以了。我很欣赏这一区别,但我有兴趣保留lambda。我正在考虑一种设计,程序的一部分将存储大量lambda,另一部分将执行它们。这个例子只是我为调查编译器行为而编写的一个测试程序。“你的第一个问题就是MSVC对lambdas的支持很糟糕。”这与他们对lambdas的支持无关;执行所有复制的是
std::function
的实现。而且,MSVC中lambdas本身的实现似乎不支持移动lambda中捕获的值成员。六羟甲基三聚氰胺六甲醚。。。我想唯一的选择是创建一个自定义类来存储lambda(或者等待更好的std::function实现)。谢谢你的信息@彼得:这只是一些复制品;让它去吧。复制
std::function
时甚至不会发生这种情况。这纯粹是初始化时间。你真的想在lambda中存储大型结构吗?只要使用它,如果你分析并发现了一个实际的性能问题,那么就解决它。这很公平。我可能是
Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
int main(){
    Simple test( 5 );
    std::function<int ()> f =[test = std::move(test)](){
        return test.Get();
    };
    printf( "%d\n", f() );
}
Constructing simple!
Moving simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
[test = std::move(test)]