条款05:了解C++默默编写并调用哪些函数
class Empty{
public:
Empty(){...} //默认构造函数
Empty(const Empty& rhs){...} //复制构造函数
~Empty(){...} //析构函数
Empty& operator=(const Empty& rhs){...} //复制赋值操作符
};
编译器产出的析构函数是non-virtual,除非这个类的基类自身声明有virtual析构函数
复制构造函数和复制赋值操作符,编译器只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
阻止复制构造函数,将复制构造函数或复制赋值操作符声明为private,且没有定义
class HomeForSale{
public:
...
private:
HomeForSale(const HomeForSale&) //只有声明
HomeForSale& operator=(const HomeForSale&)
};
- 为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现
条款07:为多态基类声明virtual析构函数
factory函数:返回一个基类指针,指向新生成的派生类对象
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock:public TimeKeeper{...};
class WaterClock:public TimeKeeper{...};
class WristClock:public TimeKeeper{...};
TimeKeeper* getTimeKeeper(); //返回一个指针,指向一个
//TimeKeeper派生类是动态分配对象
TimeKeeper* ptk=getTimeKeeper(); //从TimeKeeper继承体系获得一个动态分配对象
...
delete ptk;
问题:getTimeKeeper返回的指针指向一个派生类对象(如:AtomicClock),而那个对象经由一个基类指针删除,而目前基类有个non-virtual析构函数。
解决:给基类有个virtual析构函数
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
};
TimeKeeper* ptk=getTimeKeeper();
...
delete ptk;
virtual函数的目的是允许派生类的实现得以客制化
如果类不含virtual函数,通常表示它并不意图被用做一个基类。但类不企图当做基类,令其析构函数为virtual往往是个馊主意
- 带多态性质的基类应该声明一个virtual析构函数。如果类带有任何virtual函数,就应该拥有一个virtual析构函数。
- 类的设计目的如果不是作为基类使用,或不是为了具有多态性,不应该声明virtual析构函数
条款08:别让异常逃离析构函数
C++不喜欢析构函数吐出异常!
class DBConnection{
public:
...
static DBConnection create();
void close();
};
class DBConn{
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
只要close调用成功,一切都美好。但如果调用导致异常,DBConn析构函数会传播异常,也就是允许它离开这个析构函数
有两个办法能避免这个问题:
(1)抛出异常就结束程序
DBConn::~DBConn()
{
try{
db.close();
}
catch(...){
std::abort(); //通过调用abort完成
}
}
(2)吞下异常
DBConn::~DBConn()
{
try{
db.close();
}
catch(...){ }
}
较佳的策略是重新设计接口,使其客户有机会对可能出现的问题作出反应。
class DBConn{
public:
...
void close()
{
db.close(); //客户机使用的新函数
closed=true;
}
~DBConn()
{
if(!closed)
{
try{ //关闭连接
db.close();
}
catch(...){
...
}
}
}
private:
DBConnection db;
bool closed;
};
- 析构函数绝对不要吐出异常。
- 如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么类应该提供应该普通函数执行该操作。
条款09:绝不在构造和析构过程中调用virtual函数
class Transaction{
public:
Transaction();
virtual void logTransaction() const = 0;
};
Transaction::Transaction()
{
...
logTransaction();
}
class BuyTransaction: public Transaction{
public:
virtual void logTransaction() const;
...
};
class SellTransaction: public Transaction{
public:
virtual void logTransaction() const;
...
};
派生类对象内的基类成分会在派生类自身成分被构造之前构造妥当。
基类构造期间virtual函数绝不会下降到派生类阶层。
在派生类对象的基类构造期间,对象的类型是基类而不是派生类。
不只virtual函数会被编译器解析至基类,若使用运行期类型信息(dynamic_cast、typeid),也会把对象视为基类类型。
确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数
class Transaction{
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const; //non-virtual函数
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo); //non-virtual函数
}
class BuyTransaction: public Transaction{
public:
BuyTransaction(parameters):Transaction(createLogString(parameters)) //将log信息传给基类构造函数
{...}
...
private:
static std::string createLogString(parameters);
};
- 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类
条款10:令operator=返回一个reference to *this
int x,y,z;
x=y=z=1; //赋值连锁形式
为了实现连锁赋值,赋值操作符必须返回一个引用指向操作符的左侧实参
class Widget{
public:
...
Widget& operator=(const Widget& rhs)
{
...
return *this; //返回左侧对象
}
Widget& operator+=(const Widget& rhs)
{
...
return *this; //返回左侧对象
}
Widget& operator=(int rhs)
{
...
return *this; //返回左侧对象
}
};
- 令赋值操作符返回一个reference to *this
条款11:在operator=中处理“自我赋值”
class Bitmap{...};
class Widget{
...
private:
Bitmap* pb; //指向一个从heap分配而得的对象
};
Widget& Widget::operator=(const Widget& rhs) //不安全!!!!
{
delete pb; //停止使用当前的bitmap
pb=new Bitmap(*rhs.pb); //使用rhs的bitmap副本
return *this;
}
operator=函数内的*this和rhs有可能是同一对象
阻止错误的方式:认同测试
Widget& Widget::operator=(const Widget& rhs)
{
if(this==&rhs)
return *this; //认同测试:如果是自我赋值,就不做任何事
delete pb;
pb=new Bitmap(*rhs.pb);
return *this;
}
让operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报
我们只需要注意在复制pb所指东西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig=pb; //记住原先的pb
pb=new Bitmap(*rhs.pb); //令pb指向*pb的一个副本
delete pOrig; //删除原先的pb
return *this;
}
operator=内手工排列语句:使用copy and swap技术
class Widget{
...
void swap(Widget& rhs); //交换*this和rhs的数据
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); //为rhs制作一份副本
swap(temp); //将*this数据和上述附件的数据交换
return *this;
}
- 确保当对象自我赋值时operator=有良好的行为
条款12:复制对象时勿忘其每一个成分
void logCall(const std::string& funcName);
class Customer{
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
std::string name;
};
Customer::Customer(const Customer& rhs):name(rhs.name) //复制rhs的数据
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs) //复制rhs的数据
{
logCall("Customer copy operator");
name=rhs.name;
return *this;
}
/
class PriorityCustomer:public Customer{ //继承类
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs)
...
private:
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs),priority(rhs.priority) //调用基类的复制构造函数
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy operator");
Customer::operator=(rhs); //对基类成分进行赋值操作
priority=rhs.priority;
return *this;
}
编写一个复制构造函数时,确保:
(1)复制所有的局部成员变量
(2)调用所有基类内的适当的复制构造函数
- 复制构造函数应该确保复制对象内的所有成员变量及所有基类成分
- 不要尝试以某个复制构造函数实现另一个复制构造函数。应该将共同机能放进第三个函数中,并由两个复制构造函数共同调用