如何使c++ lambda对象的存储更高效?
How can I make the storage of C++ lambda objects more efficient?
我最近一直在考虑存储c++ lambda。您在互联网上看到的标准建议是将lambda存储在std::函数对象中。然而,这些建议都没有考虑到存储的影响。我突然想到,这背后一定有什么重要的巫术在起作用。考虑以下存储整数值的类:
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( "%dn", f() );
}
这是我希望从这个程序中看到的输出:
Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
首先,我们创建值测试。我们在堆栈上为临时lambda对象创建一个本地副本。然后将临时lambda对象移动到由std::函数分配的内存中。我们销毁临时的。我们打印输出。我们销毁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)是否有某种方法可以强制编译器生成更好的代码?感谢阅读!
您在网上看到的标准建议是将lambda存储在std::function对象中。然而,这些建议都没有考虑到存储的含义。
那是因为它不重要。您无法访问lambda的类型名。所以当你可以用auto
存储它的原生类型时,它不会用那个类型离开那个作用域。你不能以那种类型返回。你可以只能把它插到别的地方。c++ 11提供的唯一的"其他东西"是std::function
。
所以你有一个选择:暂时持有它与auto
,锁定在该范围内。或者把它放在std::function
中长期保存。
这些复制真的有必要吗?
技术?不,对于std::function
所做的,它不是必需的。
是否有办法强制编译器生成更好的代码?
。这不是编译器的错;这就是std::function
的具体实现方式。它可以做更少的复制;它不应该复制超过两次(并且取决于编译器如何生成lambda,可能只有一次)。
我注意到MSVC10的同样的性能问题,并在microsoftconnect上提交了一个bug报告:
https://connect.microsoft.com/VisualStudio/feedback/details/649268/std-bind-and-std-function-generate-a-crazy-number-of-copy
该错误被关闭为"fixed"。与MSVC11开发人员预览您的代码,现在确实打印:
Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
您的第一个问题很简单,即MSVC对std::function
的实现效率低下。在g++ 4.5.1中,我得到:
Constructing simple!
Copying simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
这仍然会创建一个额外的副本。问题是,您的lambda按值捕获test
,这就是您拥有所有副本的原因。试一试:
int main()
{
Simple test( 5 );
std::function<int ()> f =
[&test] () // <-- Note added &
{
return test.Get();
};
printf( "%dn", f() );
}
再次使用g++,我现在得到:
Constructing simple!
5
Destroying simple!
请注意,如果你通过引用捕获,那么你必须确保test
在f
的生命周期内保持存活,否则你将使用一个被销毁对象的引用,这将引发未定义的行为。如果f
需要比test
更长寿,那么你必须使用按值传递的版本。
使用c++ 14,您可以完全避免复制:
int main(){
Simple test( 5 );
std::function<int ()> f =[test = std::move(test)](){
return test.Get();
};
printf( "%dn", f() );
}
输出:
Constructing simple!
Moving simple!
Moving simple!
Destroying simple!
5
Destroying simple!
Destroying simple!
注意下面这行:
[test = std::move(test)]
这里,第一次出现"test"
问题是std::函数不使用move语义,并且在初始化时复制lambda。这是ms的一个糟糕的实现
这里有一个小技巧可以用来解决这个问题。
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));
}
用法:
int main()
{
Simple test( 5 );
std::function<int ()> f(make_move_lambda(
[test] ()
{
return test.Get();
}));
printf( "%dn", f() );
}
- 将字符串存储在c++中的稳定内存中
- C++中高效的大型稀疏块压缩线性方程
- std::原子加载和存储都需要吗
- C++:将控制台输出存储在宏中更好吗
- C++中的高效循环缓冲区,它将被传递给C样式数组函数参数
- 使用QProcess执行命令,并将结果存储在QStringList中
- 访问存储在向量C++中的结构的多态成员
- 如何从存储在std::映射中的std::集中删除元素
- 存储模板类型以强制转换回派生<T>
- 类型总是使用其大小存储在内存中吗
- C++中特征对角矩阵类型的高效存储
- 如何高效/正确地存储等距游戏的不同类(单个超类的所有子类型)的地图?
- 为什么C++不使用集中存储类型信息以实现高效的 RTTI
- 实现用于在图形中存储边缘的高效容器
- 用于存储 3d 点的高效数据结构
- 高效访问存储为 1D 阵列的 3D 阵列
- 如何使c++ lambda对象的存储更高效?
- 如何高效存储时间序列数据
- 高效存储多达 2048 个字符的数组
- 数据结构+算法为ipv4存储高效搜索前缀