隐式转换 VS 显式转换

26

《C++标准库》(作者:Nicolai M. Josuttis)指出:

下面两种写法有细微的差别:

X x;
Y y(x) //explicit conversion

并且

X x;
Y y = x; //implicit conversion

如下所述:“前者使用从类型X进行的显式转换创建类型Y的新对象,而后者通过使用隐式转换创建类型Y的新对象。”

我有点困惑于显式转换和隐式转换的概念。在两种情况下,你都将一个X推进了一个Y - 一个使用了Y的构造函数,另一个使用了赋值运算符。

这两种情况下的转换方式有什么区别?什么使它成为显式或隐式转换?如果有的话,这与使用“explicit”关键字定义类构造函数有何关联?


可能是[初始化:括号 vs. 等号]的重复问题。(https://dev59.com/9G855IYBdhLWcg3wNhd1) - Cheers and hth. - Alf
2
请注意,这两种情况都是调用构造函数,"="在这种情况下不是赋值运算符,而是声明语法的一部分。 - Cheers and hth. - Alf
1
这个不应该被关闭。他犯了一个错误,但这与差异无关,而是涉及到隐式和显式的区别。 - Puppy
4个回答

34
一个使用Y的构造函数,一个使用赋值运算符。在第二种情况下,它不是赋值,而是初始化,赋值运算符(operator=)从未被调用;相反,调用了一个非显式(explicit)的单参数构造函数(接受类型X作为参数)。
初始化和赋值之间的区别很重要:在第一种情况下,正在创建一个新对象,并且它以它正在被初始化的值开始它的生命周期(因此调用构造函数),而赋值发生在将一个对象分配(~复制)给已经存在并且已经处于确定状态的对象上。
无论如何,你写的两种初始化形式的区别在于,在第一种情况下,你显式地调用了一个构造函数,因此任何构造函数都是可以接受的;而在第二种情况下,你使用了初始化语法,而不是“传统”的构造函数语法,所以隐式地调用了一个构造函数。
在这种情况下,只有未标记为显式(explicit)的单参数构造函数是可以接受的。这样的构造函数被一些人称为“转换”构造函数,因为它们参与了隐式转换。
正如在另一个答案中指定的那样,任何未标记为显式(explicit)的构造函数都可以参与隐式转换,例如将传递给函数的对象转换为该函数期望的类型。实际上,你可以说这就是在你的第二个例子中发生的事情:你想用x初始化(=创建一个从别处复制的值)y,但首先必须将x转换为类型Y,这是通过隐式构造函数完成的。

这种隐式转换通常是有用的:例如,考虑一个字符串类,它有一个从const char *转换(即非explicit构造函数)的构造函数:任何接收string参数的函数也可以用“普通”的C字符串调用:由于转换构造函数,调用者将使用C字符串,被调用者将接收其string对象。

然而,在某些情况下,单个参数的构造函数可能不适合进行转换:通常在它们唯一的参数没有在概念上“转换”为要创建的对象类型时会发生这种情况,而只是作为构造的参数;例如,考虑一个文件流对象:它可能有一个接受要打开的文件名的构造函数,但说这样的字符串被“转换”为在该文件上工作的流是毫无意义的。

你还可以找到一些更复杂的场景,这些隐式转换可能会完全混乱程序员从重载解析中期望的行为;在我上面链接的答案下面可以找到这方面的例子。

更简单地说,有时候某些构造函数可能很重量级,因此类设计人员可能希望确保它们被显式调用。在这些情况下,构造函数被标记为explicit,因此它只能在“显式地作为构造函数”调用时使用,不参与隐式转换。


4
第一种形式是直接初始化。第二种是复制初始化
复制初始化隐式调用转换构造函数或转换运算符,然后显式调用复制构造函数(复制构造函数调用可能被省略,但仍必须执行可访问性检查)。
考虑第三种可能性,即复制初始化,但转换是显式的:
Y y = Y(x);

或者

Y y = (Y)x;

你能提供更多细节吗?当你说隐式调用转换构造函数时,是否意味着像STL使用的模板构造函数来提供隐式类型转换?再次问一下,什么使它隐式/显式 - 你在答案中使用的术语让我感到困惑。谢谢你,我看到你在正确的轨道上,我只需要简化一下。 - John Humphreys
3
隐式转换是编译器为了匹配类型而添加的转换。显式转换是指使用强制类型转换语法(包括函数式转换,它看起来像构造函数调用)的情况。在第一种情况中,你有一个显式构造函数调用(参数在括号内,看起来像函数调用)。在第二种情况中,复制构造函数调用被认为是显式的,但参数需要强制转换为正确的类型,由于在源代码中没有放置强制类型转换,所以这个强制转换是隐式的。 - Ben Voigt
1
简单来说:显式意味着您请求了转换。隐式意味着您没有请求转换,它是由于您的代码要求执行其他操作而添加的。 - Ben Voigt
感谢提供额外的细节,这解决了很多问题。由于这些评论/解决方案有所帮助,我已经提高了它们的投票。 :) - John Humphreys

4

尽管这个操作符被使用,但它实际上直接调用构造函数。

之所以一个是显式的,一个是隐式的,是因为在不希望发生转换时会发生隐式转换,而显式转换则不会。最简单的例子就是bool类型。

假设你发明了一种类型,可以是true或false-比如指针。然后你决定为了方便你的用户,让它可以隐式地转换为bool类型。这很好——直到其中一个用户做了一些愚蠢的事情。

int i = 0;
i = i >> MyUDT();

等等,为什么那段代码可以编译通过?MyUDT 无法进行移位操作啊!这是因为 bool 是一种整型。编译器将其隐式转换为一个 bool,然后再转换成可以移位的类型。上面的代码显然很愚蠢——我们只想让人们能够转换为一个 bool,而不是转换成一个 bool 和其他任何 bool 可能想做的事情。

这就是为什么 C++0x 添加了显式转换运算符的原因。


0

隐式转换不需要任何转换运算符。通常在将数据从较小的整数类型转换为较大的整数类型或派生类型转换为基类型时使用此转换。

int iVal = 100; double dVal = iVal;

显式转换构造函数优于隐式转换运算符,因为在后一种情况下还需要调用复制构造函数。隐式和显式转换


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接