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

时间:2012-06-17 07:20:08

标签: c++ c++11 initialization copy-elision

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

C x = factory();
C y( factory() );
C z{ factory() };

在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
}

编辑:如果有任何疑问,请按以下方式解析此return语句:

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

      

    13:初始化的形式(使用括号或=)通常是微不足道的,但是当   初始化器或正在初始化的实体具有类类型;见下文。如果正在初始化的实体没有   如果有类型,带括号的初始值设定项中的表达式列表应该是单个表达式。

         

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

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

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

    return { list }语句的 copy-list-initialization 规范仅指定了temp T = { list };的确切等效语法,其中=表示复制初始化。它不会立即暗示调用复制构造函数。

    - 结束编辑。


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

    C && x = factory(); // applies to other initialization syntax
    

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

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

2 个答案:

答案 0 :(得分:2)

关于你的主要问题:

  

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

你没有。

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

换句话说,这是:

C c = {...};

与此不同:

C c = [&]() -> C {return {...};}()

您有一个按值返回类型的函数。它返回类型为C的prvalue表达式。如果要存储此值,从而为其命名,则您有两个选项:

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

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

  3. 如果要存储prvalue表达式,这些是C ++可用的唯一选项。所以你可以使类型可移动或返回一个新分配的指向内存的指针并存储而不是值。

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

答案 1 :(得分:1)

这样的问题属于prime motivations change in C++17允许这些初始化(并从语言中排除副本,而不仅仅是作为优化)。