如何使C++ lambda对象的存储更有效?

Pet*_*man 21 c++ c++11

我一直在考虑最近存储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;
};
Run Code Online (Sandbox Code Playgroud)

现在,考虑这个简单的程序:

int main()
{
    Simple test( 5 );

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

    printf( "%d\n", f() );
}
Run Code Online (Sandbox Code Playgroud)

这是我希望从这个程序中看到的输出:

Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
Run Code Online (Sandbox Code Playgroud)

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

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

Constructing simple!
Copying simple!
Copying simple!
Copying simple!
Copying simple!
Destroying simple!
Destroying simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
Run Code Online (Sandbox Code Playgroud)

神圣的废话是低效的!编译器不仅没有使用我的移动构造函数,而且在赋值期间它生成并销毁了两个明显多余的lambda副本.

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

Nic*_*las 10

您在Internet上看到的标准建议是将lambda存储在std :: function对象中.但是,这些建议都没有考虑存储的影响.

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

所以你有一个选择:暂时坚持auto,锁定在该范围内.或者将其粘在std::function长期存放中.

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

技术上?不,没有必要做什么std::function.

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

不.这不是编译器的错; 这就是这个特定的实现方式std::function.它可以减少复制; 它不应该复制超过两次(并且取决于编译器如何生成lambda,可能只有一次).但确实如此.


Tho*_*tit 8

前一段时间我用MSVC10注意到了同样的性能问题,并在microsoft connect上提交了一个错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/649268/std-bind-and-std-function-generate-
一个疯狂的用户号码的副本#细节

该错误被关闭为"固定".使用MSVC11开发人员预览您的代码现在确实打印:

Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
Run Code Online (Sandbox Code Playgroud)


Pet*_*der 6

你的第一个问题就是MSVC的实现std::function效率低下.使用g ++ 4.5.1,我得到:

Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
Run Code Online (Sandbox Code Playgroud)

那仍然是创造一个额外的副本.问题是你的lambda是test按值捕获的,这就是你拥有所有副本的原因.尝试:

int main()
{
    Simple test( 5 );

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

    printf( "%d\n", f() );
}
Run Code Online (Sandbox Code Playgroud)

再次使用g ++,我现在得到:

Constructing simple!
5
Destroying simple!
Run Code Online (Sandbox Code Playgroud)

请注意,如果通过引用捕获,则必须确保在test生命周期内保持活动状态f,否则您将使用对已销毁对象的引用,这会引发未定义的行为.如果f需要寿命更长,test那么你必须使用pass by value版本.

  • 如果要存储lambda,通过引用捕获是一个非常糟糕的主意. (2认同)