假设以下代码是可以正确编译的合法代码,即T
类型名称,即x
变量名称。
语法一:
T a(x);
语法二:
T a = x;
这两个表达式的确切语义是否有所不同?如果有,在什么情况下?
如果这两个表达式确实有不同的语义,我也很好奇标准的哪一部分谈到了这一点。
此外,如果在 T 是标量类型的名称(又名、、、、等)时存在特殊情况int
,long
那么double
当 T 是标量类型与非标量类型时有什么区别?
假设以下代码是可以正确编译的合法代码,即T
类型名称,即x
变量名称。
语法一:
T a(x);
语法二:
T a = x;
这两个表达式的确切语义是否有所不同?如果有,在什么情况下?
如果这两个表达式确实有不同的语义,我也很好奇标准的哪一部分谈到了这一点。
此外,如果在 T 是标量类型的名称(又名、、、、等)时存在特殊情况int
,long
那么double
当 T 是标量类型与非标量类型时有什么区别?
是的。如果 x 的类型不是T
,则第二个示例扩展为T a = T(x)
。这就要求T(T const&)
是公开的。第一个示例不调用复制构造函数。
检查可访问性后,可以删除副本(正如托尼指出的那样)。但是,在检查可访问性之前无法消除它。
从 8.5.14 开始(强调我的):
以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的临时值。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;请参见 class.temporary、class.copy。
因此,它们是否等效取决于实现。
8.5.11 也是相关的,但仅在确认可能存在差异时:
-11- 初始化的形式(使用括号或=)通常是无关紧要的,但当被初始化的实体具有类类型时,它就很重要;见下文。仅当被初始化的实体具有类类型时,带括号的初始化程序才能是表达式列表。
这里的区别在于隐式和显式构造之间,并且可能存在差异。
想象一下有一个Array
带有构造函数的类型Array(size_t length)
,而在其他地方,你有一个函数count_elements(const Array& array)
。这些的目的很容易理解,并且代码似乎足够可读,直到您意识到它允许您调用count_elements(2000)
. 这不仅是丑陋的代码,而且还会无缘无故地在内存中分配一个 2000 个元素长的数组。
此外,您可能还有其他类型可以隐式转换为整数,从而允许您也对这些类型运行 count_elements() ,从而以高效率代价提供完全无用的结果。
你想要在这里做的是声明Array(size_t length)
一个显式的构造函数。这将禁用隐式转换,并且Array a = 2000
不再是合法的语法。
这只是一个例子。一旦您意识到explicit
关键字的作用,就很容易想到其他人。
T a(x)
是直接初始化,T a = x
是复制初始化。
从标准:
8.5.11 初始化的形式(使用括号或=)通常是无关紧要的,但是当被初始化的实体具有类类型时,它就很重要;见下文。仅当被初始化的实体具有类类型时,带括号的初始化程序才能是表达式列表。
8.5.12 在参数传递、函数返回、抛出异常(15.1)、处理异常(15.3)和大括号括起来的初始化列表(8.5.1)中发生的初始化称为复制初始化,等效于形式
T x = a;
在 new 表达式 (5.3.4)、static_cast 表达式 (5.2.9)、函数符号类型转换 (5.2.3) 以及基和成员初始化程序 (12.6.2) 中发生的初始化称为直接初始化,等效于表格
T x(a);
不同之处在于复制初始化创建了一个临时对象,然后用于直接初始化。允许编译器避免创建临时对象:
8.5.14 ...然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8。
复制初始化需要一个非显式构造函数和一个可用的复制构造函数。
在 C++ 中,当您编写此代码时:
class A {
public:
A() { ... }
};
编译器实际上会生成这个,具体取决于您的代码使用什么:
class A {
public:
A() { ... }
~A() { ... }
A(const A& other) {...}
A& operator=(const A& other) { ... }
};
所以现在你可以看到各种构造函数的不同语义。
A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator
复制构造函数基本上复制所有非静态数据。它们仅在生成的代码合法且健全的情况下才会生成:如果编译器在类中看到他不知道如何复制的类型(根据正常的分配规则),则不会生成复制构造函数。这意味着如果 T 类型不支持构造函数,或者如果类的公共字段之一是 const 或引用类型,例如,生成器将不会创建它们 - 并且代码将不会构建。模板在构建时扩展,因此如果生成的代码不可构建,它将失败。有时它会大声且非常神秘地失败。
如果在类中定义构造函数(或析构函数),生成器不会生成默认值。这意味着您可以覆盖默认生成的构造函数。您可以将它们设为私有(默认情况下它们是公共的),您可以覆盖它们以便它们什么都不做(有助于节省内存和避免副作用)等。