|
发表于 2020-6-14 18:00:01
|
显示全部楼层
转载:
本文所说的类型T均指UDT,非built-in类型
感谢ilovecpp,所有“*注”部分由他提醒而补充
构造一个对象,有如下三种形式:
1。T a;
这个没什么好说的,调用default ctor来构造a
不过要注意的是,要么T就一个ctor也没有,编译器合成default ctor,即T::T()
如果T有手动添加的其他形式的ctor,但是没有T::T(),则此语句报错,因为编译器不再
为T合成default ctor
*注1,如果没有default ctor,但是有某个ctor的所有参数都有缺省值,则T a;也成立
2。T a(v);
这个语句显式用v作为参数调用T的某个最适合的可单参数调用的ctor来构造a
该语句形式被C++标准称为direct-initialization
这里强调两点,一个是显式,这说明这种方式构造a的话可以调用被explicit声明的ctor
第二点是最适合,这是由overload rules决定的,而不是那么想当然,示例代码如下
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
T(T&){}
};
T foo() {return T();}
int main()
{
T a;
T b(a); // 编译出错,最适合的T(T&)不可访问,为private
T c(foo());// 编译成功,foo()返回的临时对象是rvalue,不能绑定到non-const引用
// 因此T(T&)不是由overload rules选定的最适合ctor
// 但是rvalue可以调用一次自定义隐式转换cast到int然后调用T(int)构造c
// 也就是说,overload rules选择了T(int)
return 0;
}
这段代码说明,即使类型相同,调用的也不一定是copy ctor
如果有人怀疑T(T&)不是copy ctor,认为T(const T&)才是,请参阅C++标准12.8
3。T a = v;
该语句形式被C++标准称为copy-initialization,这个名称有很大的迷惑性,导致本人曾
一度认为该式语义上(稍后解释什么是语义上)必须要调用copy ctor
参照C++标准8.5/14,有这么一句话
If the destination type is a (possibly cv-qualified) class type:
--If the initialization is direct-initialization, or if it is
copy-initialization where the cv-unqualified version of the
source type is the same class as, or a derived class of, the
class of the destination, constructors are considered. The
applicable constructors are enumerated (13.3.1.3), and the
best one is chosen through overload resolution (13.3). The
constructor so selected is called to initialize the object,
with the initializer expression(s) as its argument(s). If no
constructor applies, or the overload resolution is ambiguous,
the initialization is ill-formed.
简单点说,上面的意思就是,如果T是UDT,在“copy-initialization且v的cv-
unqualified类型也为T(或者T的派生类)”的情况下,执行方式同
direct-initialization一样!
*注2,实际上该情况中的copy-initialization同direct-initialization还是有区别的,
* C++标准指出,只有T a(v);的方式才是显式调用ctor,否则为隐式调用,后者要求
* 被选中的ctor不能被声明为explicit,否则编译出错
*---C++标准12.3.1
* An explicit constructor constructs objects just like non-explicit
* constructors, but does so only where the direct-initialization
* syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
C++标准还规定,如果非上述情况,则有
--Otherwise (i.e., for the remaining copy-initialization cases),
user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is ill-formed.
The function selected is called with the initializer expression as its
argument; if the function is a constructor, the call initializes a
temporary of the destination type. The result of the call (which is
the temporary for the constructor case) is then used to direct-initialize,
according to the rules above, the object that is the destination of
the copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
简单点说,就是语句T a = v;在当v的类型不同于T(也不是T的派生类)的时候,语义上
要先用v构造一个临时对象T(或者调用自定义转换隐式cast到T类型或者T的派生类型),
然后显式调用copy ctor来构造a
之所以强调语义上,是因为标准说了这个调用可以优化掉,但是语义存在
而强调显式,则说明copy ctor可以为explicit
此外需要强调的就是虽然标准在这里说构造了临时对象T(v)之后(或者是将v隐式转换
为类型T,static_cast<T>(v))再用direct-initialize直接构造a,整个语句总的执行
形式为T a(T(v));(static_cast<T>(v)用C语法也可记为T(v)),但是用T(v)所调用的
ctor只能是T的copy ctor,原因是编译器已经进行了一次自定义的隐式转换T(v),不能
再进行第二次,所以必须调用对T(v)直接参数匹配的ctor,这就是copy ctor
对上面的句子还有一个补充,就是派生类向基类的cast不算自定义隐式转换,是自动发
生的,所以还有一种选择就是T a(U(v));而U是T的派生类!最终调用的仍旧是copy ctor
示例如下
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
explicit T(T&){}
};
int main()
{
T a = T(); // 编译通过,相当于隐式调用T a(T());
// 而重载规则选用了T(int),即T a(static_cast<int>(T()));
// 如果T::T(int)声明为explicit,编译出错
T b = 0; // 编译出错,copy ctor不能访问,虽然被优化这个调用,但是语义尚存
return 0;
}
下面再用一个示例补充说明
struct T
{
T(int){}
explicit T(const T&){}
};
void foo(T)
{
}
int main()
{
foo(T(0)); // 编译出错,按照*注2,要求隐式拷贝给foo的参数
// 相当于T t = T(0);
foo(0); // 编译通过,用0构造一个临时对象T(0),然后显式拷贝给foo的参数
// 相当于T t = 0;
return 0;
}
由于函数的按值传参和按值返回都是copy-initialization,所以会有如上的结果差异 |
|