彻底理解c++的隐式类型转换 (2)

是的,复制省略在c++11里就已经被提到了,不过那时候它是可选的,并不强制编译器支持这一优化。因此你在GCC和clang上观察到的不一定能代表全部的c++编译器的情况,所以我们仍以标准为基础推演了理论上的行为。

到目前为止答案都是2,然而很快有意思的事情发生了——复制省略在c++17里成为了被标准化的行为。

在c++17里除非必要,否则临时量(现在叫做右值的结果对象,一个右值只有在实际需要存在一个临时变量的情况下才会创建一个临时变量,这个过程叫做实质化,创建出来的那个临时量就是该右值的结果对象)不会被创建,换而言之,T obj = expr这样的形式会以expr产生结果直接调用合适的构造函数,而不会进行临时量的创建和复制构造函数的调用,不过为了保证语义的完整性,复制构造函数仍然被要求是可访问的,毕竟类本身不允许复制构造的话复制初始化本身就是不正确的,不能因为复制省略而导致错误的代码被编译通过。

所以现在过程变成了下面这样子:

编译器发现表达式是string的复制初始化

右侧是表达式会隐式转换产生一个string的纯右值用于初始化同一类型的s

判断复制构造函数是否可用,然后发现符合复制省略的条件

寻找string里是否有符合要求的构造函数

找到了string::string(const char *),于是直接调用

s初始化完成

因此,在c++17下只会创建一个string对象,这比移动语义更加高效。这也是为什么我说题目的答案既可以是1也可以是2的原因。

同时我们还发现,在复制构造时的类型转换不管复制有没有被省略都是存在的,只不过换了一个形式,这就是我们后面要讲的内容。

隐式转换是如何工作的

复习完基础知识,我们可以进入正题了。

隐式转换可以分为两个部分,标准定义的转换和用户自定义的转换。我们先来看看它们是什么。

标准转换

也就是编译器里内置的一些类型转换规则,比如数组退化成指针,函数转换成函数指针,特定语境下要求的转换(if里要求bool类型的值),整数类型提升,数值转换,数据类型指针到void指针的转换,nullptr_t到数据类型指针的转换等。

底层const和volatie也可以被转换,只不过只能添加不能减少,可以把T*转换成const T*,但反过来是不可以的。

这些转换基本都是针对标量类型和数组这种内置的聚合类型的。

如果想要指定自定义类型的转换规则,则需要编写用户自定义类型转换的接口了。

用户自定义转换

说了这么多,也该看看用户自定义转换了。

用户能控制的自定义转换接口一共也就两个,转换构造函数和用户定义转换函数。

转换构造函数就是只类似T(T2)这样的构造函数,它拥有一个显式的T2类型的参数,通过这个构造函数可以实现从T2转换类型至T1的效果。

用户定义转换函数是类似operator T2()这样的类方法,注意不需要指定返回值。通过它可以实现从T1转换到T2。可转换的类型包括自身T1(还可附加cv限定符,或者引用)、T1的基类(或引用)以及void。

举个例子:

struct A {}; struct B { // 转换构造函数 B(int); B(const A&); // 用户定义转换函数,不需要显式指定返回值 operator A(); operator int(); }

上面的B自定义了转换规则,既可以从int和A转换成B,也可以从B转换成int和A。

不难看出规则是这样的:

T <---转换构造函数--- 其他类型 T ---用户定义转换函数---> 其他类型

这里的转换构造函数是指没有explicit限定的,有的话就不能用于隐式类型转换。

从c++11开始explicit还可以用于用户定义的转换函数,例如:

template <typename T> struct SmartPointer { //... T *ptr = nullptr; // 方便判断指针是否为空 explicit operator bool() { return ptr != nullptr; } }; SmartPointer<int> p = func(); if (p) { p << 1; // 这是不允许的 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxssx.html