条款5:了解C++默默编写并调用哪些函数
编译器可以为类暗自创建默认构造函数、复制构造函数、赋值运算符重载函数以及析构函数,所有这些函数都是public且inline。当这些函数被需要(被调用)而用户没有定义这些函数时,它们才被编译器创建出来,编译器创建的析构函数是non-virtual的。
但是,如果你打算在一个含有引用成员和const成员的类中支持赋值操作,你就必须自己定义一个赋值运算符重载函数。
条款6:若不想使用编译器自动生成的函数,就该明确拒绝
可将相应的成员函数声明为private并且只有声明不予实现。使用像Uncopyable这样的基类也是一种做法。
class Uncopyable
{
protected:
Uncopyable(){}
~Uncopyable(){} //允许继承对象的构造函数和析构函数
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&); //阻止复制
};
条款7:带多态性质的基类应该声明一个virtual析构函数:
如果类带有任何的virtual函数,它就应该拥有一个virtual析构函数。这种基类的设计目的是为了用来“通过基类接口处理继承类对象”。
类的设计目的如果不是作为基类使用,或不是为了具有多态性,就不该声明virtual析构函数。
条款8:别让异常逃离析构函数
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数就应该捕捉任何异常,然后吞下他们(不传播)或结束程序。如下:
class DBConnection
{
public:
...
static DBConnection create();
void close();//关闭连机,失败时则抛出异常
};
class DBConn() //这个类用来管理DBConnection对象
{
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
//如果析构函数抛出异常就结束程序,通常是通过调用abort完成
DBConn::~DBConn()
{
try{db.close();}
catch(...){
std::abort();
}
}
//如果析构函数吞下发生的异常
DBConn::~DBConn()
{
try{db.close();}
catch(...){
制作运转记录,记下对close的调用失败;
}
}
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数(而非在析构函数中)执行该操作。如下所示:
class DBConn
{
public:
...
void close() //供客户使用的新函数
{
db.close();
close=true;
}
~DBConn()
{
if(!close) //关闭连接(如果客户不这么做的话)
{
try{db.close();}
catch(...){
制作运转记录,记下对close的调用失败; //如果关闭动作失败,记录下来并结束程序或吞下异常
...
}
}
}
private:
DBConnection db;
bool close;
};
条款9:绝不在构造和析构函数过程中调用virtual函数
绝不在构造和析构函数中调用virtual函数,构造函数和析构函数内调用的所有函数也不能是virtual函数,以免发生不适当的virtual函数版本被调用的问题。因为这类调用从不下降至derived class。
在继承类对象的基类对象构造期间,对象的类型是base class而不是derived class,在base class构造期间,virtual函数就不是virtual函数,对象在derived class构造函数开始执行前不会成为一个derived class对象。相同的道理也适用于析构函数,一旦derived class析构函数开始执行,对象内的derived class成员变量就会呈现未定义值,进入base class析构函数后对象就成为一个base class对象。
条款10:令operator=返回一个reference to *this
条款11:在operator=中处理”自我赋值“
确保当对象自我赋值时,operator=有良好的行为。其中技术包括比较“来源对象”和“目的对象”的地址、精心周到的语句顺序、以及copy-and-swap。确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
Widget& Widget::operator=(const Widge& rhs) //注意复制pb之前别删除pb</span>
{
Bitmap* porig=pb;//记住原来的pb
pb=new Bitmap(*rhs.pb);//令pb指向*pb的一个副本
delete porig;//删除原来的pb
return *this;
}
//比较“来源对象”和“目的对象”的地址
<pre name="code" class="cpp">Widget& Widget::operator=(const Widge& rhs)
{
if(this=&rhs) return *this;
delete pb;
pb=new Bitmap(*rhs.pb);
return *this;
}
确保代码不但“异常安全”而且“自我赋值安全”的一个方案就是使用所谓的copy and swap技术。
class Widge
{
...
void swap(Widge& rhs);//交换*this和rhs的数据
...
};
Widge& Wideg::operator=(const Widge& rhs)
{
Widge temp(rhs);
swap(temp);
return *this;
}
条款12:复制对象时勿忘其每一个成分
类的复制函数应该确保复制“对象内的所有成员变量”及“所有的基类成分”。不要尝试以某个复制函数实现另一个赋值运算符重载函数,反之也是如此,应该将共同机能放进第三个函数中,由两个copying函数共同调用,来消除两函数之间的代码重复。
PC::PC(const PC&rhs):C(rhs),p(rhs.p)
{}
PC& PC::operator=(const PC& rhs)
{
C::operator=(rhs);
p=rhs.p;
return *this;
}