常见错误56: 直接和复制初始化----读书笔记《c++ gotchas》...
时间:2010-08-19 来源:lzueclipse
在作者工作的那个年代(15年前了,那时没准你还读小学),丑陋的初始化比比皆是。
class Y {
public:
Y(int);
~Y();
};
对于Y对象,有三种初始化过程:
Y a(1066);
Y b = Y(1066);
Y c = 1066;
a是直接初始化,调用Y::Y(int)。
b和c都是复制初始化。
b初始化过程是首先生成一个匿名临时对象,然后复制构造完成初始化,然后匿名临时
对象析构,类似于以下伪代码:
Y temp(1066);
Y b(temp);
temp.~Y();
c初始化过程和b完全一样,只不过产生匿名临时对象的要求不那么显著罢了。
对Y做些改动:
class Y {
public:
Y(int);
Y(const Y &)
{
abort();
}
~Y();
};
显然,Y对象对于任何复制构造函数都没有容忍度。
为什么我们编译和运行上面三个初始化语句的时间,并未引起进程终止(abort未被调用),这说明了什么?
标准允许编译器执行某种代码变换,去除这个临时对象的初始化合复制构造函数的调用,产生和
直接初始化一模一样的目标码。这是一种优化,大部分编译器都会这么做,但标准没有强制要求这么做,
所以存在不确定性。
还是采用直接初始化最好:
Y a(1066), b(1066), c(1066);
如果你脾气很倔,想实现不做代码变换(优化)时的复制初始化语义:
struct {
char b_[sizeof(Y)];
}aY;//准备一个和Y具有相同尺寸的存储块
new (&aY) Y(1066);//用placement new创建一个中间对象
Y d(reinterpret_cast (aY) );//复制构造
reterpret_cast (aY).~Y();//析构
这个也太费心了,算了吧。
下面理解下在代码变换中的一个要点:
编译器在校验过原始代码的语义后才实施这个变换,如果未经变换的初始化根本就是错的,
那么编译器会报错,不做变换。
class X {
public:
X(int);
~X();
//…
private:
X(const X &);
};
X a(1066);//正确
X b = 1066;//错误,copy构造为private
X c = X(1066);//错误,copy构造为private
直接和复制初始化对非class对象也起作用,不过结果是可预期的:
int i(12);//直接初始化
int j = 12;//复制初始化,结果相同
对于这种类型,就选看起来顺眼的就好。
在模板内,还是使用直接初始化比较好,因为那里变量的类型在具现之前都是未知的。
以下例子,形参In是迭代器类型,形参N是计数器类型,seqLength是一个简化了的计算序列长度的泛
型算法:
template
void seqLength(N &len, In b, In e) {
N n(0);//要这样写,不要写成N n = 0;
while( b != e) {
++n;
++b;
}
len = n;
}
对N进行了直接初始化,使得我们能够处理自定义的、禁止进行复制构造的数值类型。
一句话,尽量使用直接初始化。