摘自effective C++第2部分构造/析构/赋值运算
5. 了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls)
编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数
C++中,如果自己没有声明,编译器就会为它声明(编译器版本的)一个copy构造函数,一个copy assignment操作符和一个析构函数.此外,如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数.所有这些函数都是public且inline.
class Empty {
public:
Empty() {...} //default构造函数
Empty(const Empty& rhs) {...} //copy构造函数
~Empty() {...} //析构函数,是否该是virtual,见稍后说明
Empty& operator=(const Empty& rhs) {...} //copy assignment操作符
};
C++中,声明一个构造函数,编译器不再为他创建default构造函数.这很重要,意味如果你用心设计一个class,其构造函数要求实参,你就无须担心编译器会毫无挂虑地为你添加一个无实参构造函数(即default构造函数)而遮盖掉你的版本
6. 若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of complier-generated functios you do not want)
为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现.使用像Uncopyable这样的base class也是一种做法
class noncopyable
{
public:
noncopyable(const noncopyable&) = delete;
void operator=(const noncopyable&) = delete;
protected:
noncopyable() = default;
~noncopyable() = default;
};
- 为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes)
polymorphic(带多态性质的) base classea应该声明一个virtual析构函数.如果class带有任何virtual函数,他就应该拥有一个virtual析构函数
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数 - 别让异常逃离析构函数(Prevent exceptions from leaving destructors)
析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作
class DBConn {
public:
...
void close() //供客户使用的新函数
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { //关闭连接(如果客户不那么做的话)
db.close();
}
catch (...) { //如果关闭动作失败
制作运转记录,记录对close的调用失败; //记录下来并结束程序
... //或吞下异常
}
}
}
private:
DBConnection db;
bool closed;
}
- 绝不在构造和析构过程中调用virtual函数(Nerver call virtual functions during construction or destruction)
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层) - 令operator= 返回一个reference to this(Having assignment operators return a reference to this)
*令赋值(assignment)操作符返回一个reference to this
这只是个协议,并无强制性.如果不遵循它,代码一样可通过编译.然而这份协议被所有内置类型和标准程序库提供的类型string, vector, complex, trl::shared_ptr或即将提供的类型共同遵守.因此除非你有一个标新立异的好理由,不然还是随众吧 - 在opertor= 中处理"自我赋值"(Handle assignment to self in operatop=)
确保当对象自我赋值是operator=有良好行为.其中技术包括比较"来源对象"和"目标对象"的地址,精心周到的语句顺序,以及copy-and-swap
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象是,其行为仍然正确.
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
class Widget {
...
void swap(Widget& rhs);
...
};
//reference
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
//pass by value
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
- 复制对象时勿忘其每一个成分(Copy all parts of an object)
Copying函数应该确保复制"对象内的所有成员变量"及"所有base class成分"
不要尝试以某个copying函数实现另一个copying函数.应该将共同机能放进第三个函数中,并由两个coping函数共同调用