提问人:chakmeshma 提问时间:10/21/2023 最后编辑:chakmeshma 更新时间:10/23/2023 访问量:194
禁用复制/移动省略时如何有效地返回对象?
How to efficiently return an object when copy/move elision is disabled?
问:
假设在编译时禁用了复制省略,那么以下操作是否是避免不必要的复制(模拟复制省略)的有效做法?
Container getContainer() {
Container c;
return c;
}
int main() {
Container&& contianer = getContainer();
}
答:
我没有足够的资格讨论有关NRVO的标准的细微差别,但我们可以尝试与下面的代码片段类似地测试它(使用GCC 13.2和C++11模式加上选项。直播):-fno-elide-constructors
#include <vector>
#include <cstdio>
std::vector<int> create( const int in )
{
std::vector<int> foo ( 5, in );
return foo;
}
int main( int argc, char* argv[] )
{
std::vector<int>&& result { create( argc ) }; // remove `&&` and it will result
// in slightly more assembly code
for ( auto&& val : result )
std::printf( "%d\n", val );
}
似乎添加到类型中消除了对一些额外和说明的需求。话虽如此,您可能需要衡量性能差异。在某些情况下,生成的机器代码可能会产生误导。&&
result
mov
movdqa
另一方面,Clang 17.0.1 为两种情况生成相同的代码(有或没有)。&&
从 C++17 开始保证复制省略,除非您使用标志禁用,例如 gcc 中的 -fno-elide-constructors。
如果您禁用了复制省略,并且您仍然想要一种高效返回对象的方法(我认为高效是指低运行时开销),那么临时对象生存期延长是要走的路。可以使用常量左值引用或转发引用来延长临时引用的生存期。
移动省略是一回事吗?请记住,如果对返回值执行 std::move,则会弄乱 RVO 和 NRVO。当前的标准措辞目前仅将对象作为 RVO/NRVO 的返回值处理,std::move 将返回右值引用,这不是 C++ 中的对象。
评论
-fno-elide-constructors
对 C++17 的保证复制省略没有影响,因为没有省略。
从 C++17 开始,某些形式的复制省略不再是复制省略,因为该语言不再指定创建临时的。因此,编译器首先不需要省略任何东西。例如,你可以这样写:
Container container = getContainer();
并且没有为 的返回值创建临时对象。相反,程序直接将结果构造为 .因此,您不需要编写 .getContainer
container
Container&& container = getContainer()
另一方面,NRVO 不能保证。当我们看这个函数定义时:
Container getContainer() {
Container c;
return c;
}
您正在创建一个名为 的局部变量,并请求对返回对象进行移动初始化。如果编译器执行 NRVO,则省略该变量。但是,如果您希望保证省略本地对象(因此不需要移动),那么这样做的方法是首先不声明本地对象。c
c
c
Container
Container
在简单的情况下,您可以在最后一分钟创建返回值:
Container getContainer() {
// ...
return Container();
}
上面的代码没有创建临时对象(从 C++17 开始)。
但是,如果您需要构造返回值,对它执行某些操作,然后稍后返回,则可能需要声明函数以采用类型的 out-参数,该函数将突变为它想要返回的值。在某些情况下,调用方可能无法构造要传递到函数中的对象。在这种情况下,该函数必须采用指针参数,该值将使用放置 new 构造到该参数中。在这种情况下,调用方必须注意在作用域结束时手动调用析构函数。Container&
Container
上一个:转换为右值引用可防止复制省略
下一个:复制省略重叠对象
评论
main
Container&& contianer = getContainer();
顺便说一句,毫无意义。 行为完全相同,只是第一个更难理解。由于 C++17 两者都不创建副本(无论是否启用了复制省略),并且在 C++17 之前,如果未完成复制省略,它们都会创建副本。Container contianer = getContainer();