5.了解C++默默编写并调用哪些函数(know what functions C++ silently writes and calls)
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
编译器拒绝提供copy assignment操作符的2种情况:
1.有reference成员变量
2.有const成员变量
因为reference/const成员变量是不可赋值的,所以需要手动提供copy assignment操作符。
6.若不想使用编译器自动生成的函数,就改明确的拒绝(Explicitly disallow the use of compiler-generated functions you do not want)
为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。像使用Uncopyable这样的base class也是一种做法
拒绝编译器提供自动生成函数的两种方法
将要拒绝提供的函数声明为private并不予实现(因为friend和member函数可以调用private函数)。
class HomeForSale{
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};
使类继承自一个使用上述方法基类,使编译器无法提供要被拒绝的函数
class Uncopyable{
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale: private Uncopyable{
};
7.为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes.)
polymorphic(带有多态性质的)base class 应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
如果derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义。
解决方法是在base class中添加virtual析构函数。使得通过base class指针可调用derived class的析构函数。
同理我们的base class中如果有virtual function也应该声明virtual析构函数。因为derived class中vptr删除等操作应客制化。
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。因为会安插vptr增大类的体积,丧失可移植性。
8.别让异常逃离析构函数(Prevent exceptions from leaving destructors)
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。
9.绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction.)
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
若base class在构造期间调用virtual函数,则其derived class在构造期间调用的base class构造函数中调用的该virtual函数是base class版本的,因为在构造期间,base class部分先于derived class部分构造完成,当base class的构造函数调用virtual函数时,derived class尚未形成,所以调用的是base class版本的virtual函数。析构函数同理。
class Base{
public:
Base(){
virFunc();
}
virtual void virFunc() const {
printf("base class virtual function\n");
}
};
class derived: public Base{
public:
virtual void virFunc() const {
printf("derived class virtual function\n");
}
};
int main(int argc, const char * argv[]) {
derived d; //输出:base class virtual function
return 0;
}
10.另operator= 返回一个reference to *this(Have assignment operators return a reference to *this)
返回一个reference to *this将支持连续赋值:例如a = b = c;
class A{
public:
A(int val):val(val){}
A& operator=(const A& rhs){//返回reference to *this
this->val = rhs.val;
return *this;
}
int val;
};
int main(int argc, const char * argv[]) {
A a1(1);
A a2(2);
A a3(3);
a1 = a2 = a3;
std::cout<<"a1="<<a1.val<<" a2="<<a2.val<<" a3="<<a3.val<<std::endl;
return 0;
}
输出:
a1=3 a2=3 a3=3
同理,不止operator=需要返回reference to *this,例如+=,-=等操作符也该返回reference to *this。
11.在operator=中处理”自我赋值”(Handle assignment to self in operator=)
确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然确定。
自我赋值应考虑异常安全性和自我赋值安全性。如下版本的自我赋值是不安全的。
class Data{};
class A{
public:
A& operator=(const A& rhs){
delete data;//如果rhs与this是同一个对象,this将指向已删除对象。
data = new Data(*rhs.data);//如果此处发生了异常,this将指向已删除的对象。
return *this;
}
private:
Data* data;
};
解决方法是将this做一个副本,再将传入参数copy给this,在删除副本。
A& operator=(const A& rhs){
Data* temp = data;
data = new Data(*rhs.data);//如果此处发生异常,this的data不会消失。即使this与rhs指向同一对象也不会发生错误。
delete temp;
return *this;
}
12.赋值对象时勿忘其每一个成分(Copy all parts of an object)
Copying函数应该确保赋值“对象内的所有成员变量”及”所有base class成分”
不要尝试以某个copying函数实现另一个copying函数。应该将共有机能放进第三个函数中,并由两个copying函数共同调用。(不要以copy assignment调用copy constructor,反之亦然。但可以用copy constructor调用copy constructor,copy assignment同样)
自定义copying函数(包含copy构造函数和operator=和类似的函数)时要考虑每一个成员的操作,编译器不会产生警告信息或为你添加copy成员的语句。
在derived class中的自定义copying函数中要调用base class的对应copying函数(由编译器合成copying函数的会由编译器调用),因为编译器同样不会为你复制继承自base class的成员变量。写法应类似下边这样:
Derived(const Derived& rhs)
:Base(rhs)
{
//do something;
}
用copy assignment调用copy 构造函数是不合理的,因为这就像试图构造一个已经存在的对象。用copy构造函数调用copy assignment也是无意义的,若两个copying函数有共同点应把共同之处写在第三个函数中,再由两个copying函数调用。