从工厂函数就地初始化不可复制的成员(或其他对象)

Pot*_*ter 21 c++ initialization copy-elision c++11

对于任何此语法,类必须具有有效的副本或移动构造函数才是合法的:

C x = factory();
C y( factory() );
C z{ factory() };
Run Code Online (Sandbox Code Playgroud)

在C++ 03中,依赖复制省略来阻止编译器触及复制构造函数是相当普遍的.无论定义是否存在,每个类都有一个有效的复制构造函数签名.

在C++ 11中,应该定义一个不可复制的类型C( C const & ) = delete;,无论使用什么,对函数的任何引用都是无效的(对于不可移动的相同).(C++11§8.4.3/ 2).例如,GCC会在尝试按值返回此类对象时抱怨.复制省立不再有帮助.

幸运的是,我们还有新的语法来表达意图而不是依赖于漏洞.该factory函数可以返回一个braced-init-list来临时构造结果:

C factory() {
    return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Run Code Online (Sandbox Code Playgroud)

编辑:如果有任何疑问,此return语句解析如下:

  1. 6.6.3/2:"带有braced-init-list的return语句初始化了从指定的初始化列表中通过copy-list-initialization(8.5.4)从函数返回的对象或引用."
  2. 8.5.4/1:"复制初始化上下文中的列表初始化称为复制列表初始化." 3:"如果T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载解析(13.3,13.3.1.7)选择最佳构造函数."

不要被名称copy-list-initialization误导.8.5:

13:初始化的形式(使用括号或=)通常是无关紧要的,但是当初始化器或正在初始化的实体具有类类型时,它很重要; 见下文.如果正在初始化的实体没有类类型,则带括号的初始值设定项中的表达式列表应为单个表达式.

14:在表单 T x = a; 中以及在参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.5.1)时发生的初始化称为复制初始化.

当初始化器是braced-init-list时,复制初始化及其替代的直接初始化始终遵循列表初始化.添加时没有语义效果=,这是列表初始化非正式地称为统一初始化的一个原因.

存在差异:与复制初始化不同,直接初始化可以调用显式构造函数.复制初始化初始化临时并复制它以在转换时初始化对象.

的规范副本列表初始化return { list }声明仅指定了完全等效的语法是temp T = { list };,在这里=表示拷贝初始化.它不会立即暗示调用复制构造函数.

- 结束编辑.


然后可以将函数结果接收到右值引用中,以防止将临时值复制到本地:

C && x = factory(); // applies to other initialization syntax
Run Code Online (Sandbox Code Playgroud)

问题是,如何从返回不可复制,不可移动类型的工厂函数初始化非静态成员?引用技巧不起作用,因为引用成员不会延长临时的生命周期.

注意,我不考虑聚合初始化.这是关于定义构造函数.

Nic*_*las 2

关于你的主要问题:

问题是,如何从返回不可复制、不可移动类型的工厂函数初始化非静态成员?

你不知道。

您的问题是您试图混淆两件事:如何生成返回值以及如何在调用站点使用返回值。这两件事彼此没有联系。请记住:函数的定义不会影响它的使用方式(就语言而言),因为该定义不一定可供编译器使用。因此,C++ 不允许返回值的生成方式影响任何内容(除了省略之外,这是一种优化,而不是语言要求)。

换句话说,这个:

C c = {...};
Run Code Online (Sandbox Code Playgroud)

与此不同的是:

C c = [&]() -> C {return {...};}()
Run Code Online (Sandbox Code Playgroud)

您有一个按值返回类型的函数。它返回类型为 的纯右值表达式C。如果你想存储这个值,从而给它一个名字,你有两个选择:

  1. 将其存储为const&&&。这会将临时块的生命周期延长到控制块的生命周期。你不能用成员变量来做到这一点;它只能通过函数中的自动变量来完成。

  2. 将其复制/移动到一个值中。您可以使用成员变量来完成此操作,但显然要求类型可复制或可移动。

如果您想要存储纯右值表达式,这些是 C++ 提供的唯一选项。因此,您可以使类型可移动,或者返回一个新分配的内存指针并存储它而不是值。

这种限制是创建移动的一个重要原因:能够按值传递事物并避免昂贵的副本。无法更改语言以强制省略返回值。因此,他们在很多情况下降低了成本。