求问为什么尽量使用初始化列表,而不要在构造函数里赋值
1个回答
展开全部
看这样一个模板,它生成的类使得一个名字和一个 T 类型的对象的指针关联起来。 template<class T> class NamedPtr { public: NamedPtr(const string& initName, T *initPtr); ... private: string name; T *ptr; }; 因为有指针成员的对象在进行拷贝和赋值操作时可能会引起指针混乱,NamedPtr 也必须实现这些函数在写 NamedPtr 构造函数时,必须将参数值传给相应的数据成员。有两种方法来实现。 第一种方法是使用成员初始化列表:
template<class T>
NamedPtr<T>::NamedPtr(const string& initName, T *initPtr )
: name(initName), ptr(initPtr){}第二种方法是在构造函数体内赋值:
template<class T>
NamedPtr<T>::NamedPtr(const string& initName, T *initPtr){name = initName;
ptr = initPtr;}两种方法有重大的不同。 从纯实际应用的角度来看,有些情况下必须用初始化。特别是 const 和引用数据成员只能用初始化,不能被赋值。所以,如果想让NamedPtr<T>对象不能改变它的名字或指针成员,就必须遵循条款21 的建议声明成员为const:
template<class T>
class NamedPtr {public:NamedPtr(const string& initName, T *initPtr);
private:const string name;
T * const ptr;};这个类的定义要求使用一个成员初始化列表,因为 const 成员只能被初始化,不能被赋值。如果 NamedPtr<T>对象包含一个现有名字的引用,情况会非常不同。但还是要在构造函数的初始化列表里对引用进行初始化。还可以对名字同时声明const 和引用,这样就生成了一个其名字成员在类外可以被修改而在内部是只读的对象。
template<class T>
class NamedPtr {public:NamedPtr(const string& initName, T *initPtr);
private:const string& name; // 必须通过成员初始化列表
// 进行初始化
T * const ptr; // 必须通过成员初始化列表
// 进行初始化};然而前面最初的类模板不包含const 和引用成员。即使这样,用成员初始化列表还是比在构造函数里赋值要好。这次的原因在于效率。当使用成员初始化列表时,只有一个string 成员函数被调用。而在构造函数里赋值时,将有两个被调用。为了理解为什么,请看在声明NamedPtr<T>对象时都发生了些什么。对象的创建分两步:
1. 数据成员初始化。
2. 执行被调用构造函数体内的动作。
对有基类的对象来说,基类的成员初始化和构造函数体的执行发生在派生类的成员初始化和构造函数体的执行之前
对NamedPtr 类来说,这意味着string 对象name 的构造函数总是在程序执行到NamedPtr 的构造函数体之前就已经被调用了。问题只在于:string 的哪个构造函数会被调用?这取决于 NamedPtr 类的成员初始化列表。如果没有为name 指定初始化
参数,string 的缺省构造函数会被调用。当在NamedPtr 的构造函数里对name执行赋值时,会对name 调用operator=函数。这样总共有两次对string 的成员函数的调用:一次是缺省构造函数,另一次是赋值。相反,如果用一个成员初始化列表来指定name 必须用initName 来初始化,name 就会通过拷贝构造函数以仅一个函数调用的代价被初始化。 即使是一个很简单的 string 类型,不必要的函数调用也会造成很高的代价。随着类越来越大,越来越复杂,它们的构造函数也越来越大而复杂,那么对象创建的代价也越来越高。
template<class T>
NamedPtr<T>::NamedPtr(const string& initName, T *initPtr )
: name(initName), ptr(initPtr){}第二种方法是在构造函数体内赋值:
template<class T>
NamedPtr<T>::NamedPtr(const string& initName, T *initPtr){name = initName;
ptr = initPtr;}两种方法有重大的不同。 从纯实际应用的角度来看,有些情况下必须用初始化。特别是 const 和引用数据成员只能用初始化,不能被赋值。所以,如果想让NamedPtr<T>对象不能改变它的名字或指针成员,就必须遵循条款21 的建议声明成员为const:
template<class T>
class NamedPtr {public:NamedPtr(const string& initName, T *initPtr);
private:const string name;
T * const ptr;};这个类的定义要求使用一个成员初始化列表,因为 const 成员只能被初始化,不能被赋值。如果 NamedPtr<T>对象包含一个现有名字的引用,情况会非常不同。但还是要在构造函数的初始化列表里对引用进行初始化。还可以对名字同时声明const 和引用,这样就生成了一个其名字成员在类外可以被修改而在内部是只读的对象。
template<class T>
class NamedPtr {public:NamedPtr(const string& initName, T *initPtr);
private:const string& name; // 必须通过成员初始化列表
// 进行初始化
T * const ptr; // 必须通过成员初始化列表
// 进行初始化};然而前面最初的类模板不包含const 和引用成员。即使这样,用成员初始化列表还是比在构造函数里赋值要好。这次的原因在于效率。当使用成员初始化列表时,只有一个string 成员函数被调用。而在构造函数里赋值时,将有两个被调用。为了理解为什么,请看在声明NamedPtr<T>对象时都发生了些什么。对象的创建分两步:
1. 数据成员初始化。
2. 执行被调用构造函数体内的动作。
对有基类的对象来说,基类的成员初始化和构造函数体的执行发生在派生类的成员初始化和构造函数体的执行之前
对NamedPtr 类来说,这意味着string 对象name 的构造函数总是在程序执行到NamedPtr 的构造函数体之前就已经被调用了。问题只在于:string 的哪个构造函数会被调用?这取决于 NamedPtr 类的成员初始化列表。如果没有为name 指定初始化
参数,string 的缺省构造函数会被调用。当在NamedPtr 的构造函数里对name执行赋值时,会对name 调用operator=函数。这样总共有两次对string 的成员函数的调用:一次是缺省构造函数,另一次是赋值。相反,如果用一个成员初始化列表来指定name 必须用initName 来初始化,name 就会通过拷贝构造函数以仅一个函数调用的代价被初始化。 即使是一个很简单的 string 类型,不必要的函数调用也会造成很高的代价。随着类越来越大,越来越复杂,它们的构造函数也越来越大而复杂,那么对象创建的代价也越来越高。
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询