正如标题所示,我正在编写一个带有类模板和几个非模板运算符重载的静态库。类模板在ah中定义,函数在a.cc中定义。
所以我决定继续问 RVO/NRVO 是否可以使库的用户代码受益?
编辑:对此我很抱歉,这是我刚刚问的另一个问题,不应该放在这个问题中。为了让场景更清晰,我实际上是在尝试封装 uint8_t 等类型,并计划自己编写一些大型整数类型。
正如标题所示,我正在编写一个带有类模板和几个非模板运算符重载的静态库。类模板在ah中定义,函数在a.cc中定义。
所以我决定继续问 RVO/NRVO 是否可以使库的用户代码受益?
编辑:对此我很抱歉,这是我刚刚问的另一个问题,不应该放在这个问题中。为了让场景更清晰,我实际上是在尝试封装 uint8_t 等类型,并计划自己编写一些大型整数类型。
当一个函数返回一个纯右值时,这意味着(C++17 之前)返回值是一个临时对象。所以,这个临时对象发生了两件事:
按照标准,如果你这样做return Type(...);
,返回表达式会被计算,产生一个临时的,这个临时用于复制初始化返回值对象。
Type
但是,标准规定如果与返回的纯右值对象的类型相同,则不必进行此复制初始化。在这种情况下,编译器将简单地将临时初始化直接应用于返回值对象。
按照标准,如果你这样做Type var = some_func(...)
, 并some_func
返回一个值,那么由返回的临时值some_func
将用于复制初始化var
。
Type
但是,该标准表示,如果是返回的值的类型,则不必进行复制初始化some_func
。因此,由它初始化的返回值对象some_func
就是var
它本身。不是一些初始化的临时对象var
;some_func
直接初始化var
。
这两个过程是完全相互独立的。
返回值初始化的省略是基于函数的实现。该实现不关心调用者在做什么。它只是直接初始化返回值对象,而不是从返回表达式中复制。
从函数的返回值中省略变量的初始化并不关心函数的实现。它仅基于函数返回一个纯右值,并且该纯右值的类型与由它初始化的对象的类型相同的事实。它只需要查看函数的声明即可完成省略操作的部分。
当这两种情况都发生时,您将完全删除从函数内部到其最终目的地的所有副本。但两者都不要求对方存在。
因此,请考虑以下事项:
Type foo()
{
Type t;
return t;
}
T t2 = foo();
按照标准,这是两次复制初始化。foo
首先,通过从 移动来初始化的返回值t
。其次,t2
是通过从 的返回值移动来初始化的foo
。
如果编译器可以忽略这两个,那么你得到 0 步。如果编译器可以省略 NRVO 的初始化t2
但不执行 NRVO t
,那么你会得到 1 move`。如果它也做不到,那么你会得到 2 步(你应该立即停止使用那个编译器;))。
如果您想要更多的实现细节,那么它与函数调用约定和 ABI 有关。
函数参数和返回值的存储空间由调用者分配。因此,调用者看到该函数将返回一个纯右值,因此它分配了足够的适当对齐方式的存储空间来存储该值,然后使用指向该存储空间的指针调用该函数。该函数的实现将在初始化返回值时使用该存储。
省略,在函数实现方面,只是直接在返回值内存中构造对象。省略,在使用返回值的函数方面,只是简单地传递将使用的对象的存储空间。上面的t2
示例将通过将 storaget2
作为返回值 storage 传递给 来执行省略foo
。
的编译器foo
不需要知道或关心返回值存储是命名值还是临时值。它所知道的是,它已经被赋予了构造返回值的存储空间。
调用者的编译器foo
只需要函数签名,因为它告诉了它执行这种省略所需知道的一切。