假设以下代码是正确编译的合法代码,T
是类型名称,x
是变量的名称。
语法一:
T a(x);
语法二:
T a = x;
这两个表达式的确切语义是否有所不同?如果是这样,在什么情况下?
如果这两个表达式确实有不同的语义,我也很好奇标准的哪一部分谈到这个。
此外,如果有一种特殊情况,当T是标量类型的名称(又名,int
,long
,double
等...)时,是什么当T是标量类型与非标量类型时的差异?
答案 0 :(得分:3)
是。如果x的类型不是T
,则第二个示例会扩展为T a = T(x)
。这要求T(T const&)
是公开的。第一个示例不调用复制构造函数。
检查可访问性后,可以删除副本(正如Tony指出的那样)。但是,在检查可访问性之前,无法消除。
答案 1 :(得分:2)
从8.5.14(强调我的):
使用初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的临时函数。然后,根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过直接将中间结果构造到正在初始化的对象中来消除此直接初始化中固有的复制;见class.temporary,class.copy。
因此,它们是否等效于实施。
8.5.11也是相关的,但只是在确认可以有区别时:
-11-初始化的形式(使用括号或=)通常是无关紧要的,但在初始化的实体具有类类型时确实很重要;见下文。只有在初始化的实体具有类类型时,带括号的初始化程序才能是表达式列表。
答案 2 :(得分:2)
这里的区别在于隐式和显式构造,并且可能存在差异。
想象一下,Array
类型带有构造函数Array(size_t length)
,而在其他地方,你有一个函数count_elements(const Array& array)
。这些目的很容易理解,代码似乎足够可读,直到你意识到它可以让你调用count_elements(2000)
。这不仅是丑陋的代码,而且还会毫无理由地在内存中分配一个长度为2000的数组元素。
此外,您可能还有其他可隐式转换为整数的类型,允许您对这些类型运行count_elements(),从而以高成本效率为您提供完全无用的结果。
您要在此处执行的操作是声明Array(size_t length)
显式构造函数。这将禁用隐式转换,Array a = 2000
将不再是合法语法。
这只是一个例子。一旦您意识到explicit
关键字的作用,就很容易想到其他关键字。
答案 3 :(得分:2)
T a(x)
是直接初始化,T a = x
是复制初始化。
来自标准:
8.5.11初始化的形式(使用括号或=)通常是无关紧要的,但在初始化的实体具有类类型时确实很重要;见下文。仅当正在初始化的实体具有类类型时,带括号的初始化程序才能是表达式列表。
8.5.12在参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和大括号括起的初始化列表(8.5.1)中发生的初始化称为复制初始化并且是等效的到表格
T x = a;
新表达式(5.3.4),static_cast表达式(5.2.9),功能表示法类型转换(5.2.3)以及基本和成员初始化程序(12.6.2)中发生的初始化称为直接初始化和相当于表格
T x(a);
不同之处在于复制初始化会创建一个临时对象,然后用于直接初始化。允许编译器避免创建临时对象:
8.5.14 ...然后根据上面的规则,使用调用的结果(对于构造函数的情况来说是临时的)来直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;见12.2,12.8。
复制初始化需要非显式构造函数和复制构造函数才能使用。
答案 4 :(得分:0)
在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或引用类型,则生成器将不会创建它们 - 并且代码将不会构建。模板在构建时扩展,因此如果生成的代码不可构建,它将失败。有时它会大声地,非常隐秘地失败。
如果在类中定义构造函数(或析构函数),则生成器将不会生成默认值。这意味着您可以覆盖默认生成的构造函数。您可以将它们设为私有(默认情况下它们是公共的),您可以覆盖它们以便它们不执行任何操作(对于保存内存和避免副作用很有用)等。