一个使用Y的构造函数,一个使用赋值运算符。在第二种情况下,它不是赋值,而是初始化,赋值运算符(operator=)从未被调用;相反,调用了一个非显式(explicit)的单参数构造函数(接受类型X作为参数)。
初始化和赋值之间的区别很重要:在第一种情况下,正在创建一个新对象,并且它以它正在被初始化的值开始它的生命周期(因此调用构造函数),而赋值发生在将一个对象分配(~复制)给已经存在并且已经处于确定状态的对象上。
无论如何,你写的两种初始化形式的区别在于,在第一种情况下,你显式地调用了一个构造函数,因此任何构造函数都是可以接受的;而在第二种情况下,你使用了初始化语法,而不是“传统”的构造函数语法,所以隐式地调用了一个构造函数。
在这种情况下,只有未标记为显式(explicit)的单参数构造函数是可以接受的。这样的构造函数被一些人称为“转换”构造函数,因为它们参与了隐式转换。
正如在
另一个答案中指定的那样,任何未标记为显式(explicit)的构造函数都可以参与隐式转换,例如将传递给函数的对象转换为该函数期望的类型。实际上,你可以说这就是在你的第二个例子中发生的事情:你想用x初始化(=创建一个从别处复制的值)y,但首先必须将x转换为类型Y,这是通过隐式构造函数完成的。
这种隐式转换通常是有用的:例如,考虑一个字符串类,它有一个从const char *
转换(即非explicit
构造函数)的构造函数:任何接收string
参数的函数也可以用“普通”的C字符串调用:由于转换构造函数,调用者将使用C字符串,被调用者将接收其string
对象。
然而,在某些情况下,单个参数的构造函数可能不适合进行转换:通常在它们唯一的参数没有在概念上“转换”为要创建的对象类型时会发生这种情况,而只是作为构造的参数;例如,考虑一个文件流对象:它可能有一个接受要打开的文件名的构造函数,但说这样的字符串被“转换”为在该文件上工作的流是毫无意义的。
你还可以找到一些更复杂的场景,这些隐式转换可能会完全混乱程序员从重载解析中期望的行为;在我上面链接的答案下面可以找到这方面的例子。
更简单地说,有时候某些构造函数可能很重量级,因此类设计人员可能希望确保它们被显式调用。在这些情况下,构造函数被标记为explicit
,因此它只能在“显式地作为构造函数”调用时使用,不参与隐式转换。