一个类必须有一个有效的复制或移动构造函数,这样的任何语法都是合法的:
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
函数可以返回一个花括号初始化列表来临时就地构造结果:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
编辑:如果有任何疑问,此return
语句解析如下:
- 6.6.3/2:“带有花括号初始化列表的返回语句通过指定初始化列表中的复制列表初始化 (8.5.4) 初始化要从函数返回的对象或引用。”
- 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)中发生的初始化称为复制初始化。
当初始化器是花括号初始化列表时,复制初始化及其替代方法直接初始化始终遵循列表初始化。添加 没有语义影响=
,这是列表初始化被非正式地称为统一初始化的原因之一。
有区别:与复制初始化不同,直接初始化可能会调用显式构造函数。复制初始化初始化一个临时对象,并在转换时将其复制以初始化对象。
语句的复制列表初始化规范return { list }
仅指定精确等效语法为temp T = { list };
,其中=
表示复制初始化。它并不立即暗示调用了复制构造函数。
-- 结束编辑。
然后可以将函数结果接收到右值引用中,以防止将临时复制到本地:
C && x = factory(); // applies to other initialization syntax
问题是,如何从返回不可复制、不可移动类型的工厂函数初始化非静态成员?引用技巧不起作用,因为引用成员不会延长临时成员的生命周期。
请注意,我不考虑聚合初始化。这是关于定义一个构造函数。