文章目录
条款05.默认构造函数/拷贝函数/赋值运算
编译器可以暗自为class创建默认构造函数,拷贝构造函数,赋值操作符以及析构函数
需要注意的有以下几点:
- 对于拷贝与赋值操作,注意成员的浅拷贝与深拷贝。
- 对于引用与const类型的变量,拷贝与构造函数需要自己实现,默认生成的会报错
条款06.防止编译器使用默认的构造/拷贝/赋值函数
阻止拷贝与赋值操作可以将其声明为私有函数,代码如下:
class HomeForSale{
public:
...
private:
...
HomeForSale(const HomeForSale &);
HomeForSale& operator=(const HomeForSale&);
}
也可以通过基类去实现,这种方法甚至杜绝了friend函数以及成员函数的调用,但是会导致多重继承等问题。
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale: private Uncopyable{
...
};
条款07.为多态基类声明virtual析构函数
以很常用的factory模式为例,时间基类
class TimeKeeper{
public:
virtual ~Timekeeper();
...
} ;
class AtomicClock: public TimeKeeper {...}; //原子表
class WaterClock:public TimeKeeper {...}; //水钟
class WristWatch: public TimeKeeper {...}; //腕表
TimeKeeper* ptk = getTimeKeeper(); //getTimeKeeper()函数是用于继承体系中获得一个动态分配对象。
delete ptk; //释放它,避免资源泄漏
可以看到基类的析构函数是virtual的,因为如果基类带着一个non-virtual的析构函数在派生类执行的时候会出现派生类的对象成分没被销毁。
纯虚函数可以将类转化为抽象类,用于多态,当没有任何纯虚函数时可以将析构函数申明成纯虚。
class AWOV{
public:
virtual ~AWOV()=0;
};
//需要纯虚析构函数的定义
AWOV::~AWOV(){}
- (带有多态性质的)基类应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
- Classes的设计目的不是作为base classes使用,或不是为了具备多态性(如上面的Uncopyable),就不该声明virtual析构函数。
条款08.别让异常逃离析构函数。
举个实例,使用一个class负责数据库链接
class DBConnection{
public:
...
static DBConnection create(); //用于返回DBConnection对象;
void close(); //关闭联机;失败则抛出异常
}
可以用一个类来管理DBConnection,防止忘记调用close
class DBConn{
public:
...
~DBConn()
{
db.close();
}
private:
DBConncetion db;
};
对于析构函数调用close产生异常有两种处理方式。
- close抛出异常就结束程序。通常通过abort完成
DBConn::~DBConn()
{
try{ db.close();}
catch(...){
制作运转记录,记下对close的调用失败
std::abort();
}
}
- 吞下调用close而发生的异常
DBConn::~DBConn()
{
try{ db.close();}
catch(...){
制作运转记录,记下对close的调用失败
}
}
也可以重新设置DBConn接口,提供函数是的客户有机会对可能出现的问题作出反应。
class DBConn{
public:
...
void close()
{
db.close();
closed = true;
}
~DBConn()
{
if(!closed)
{
db.close();
}
catch(...){
上面两种异常的处理方式中的一种
}
}
private:
DBConnection db;
bool closed;
}
只是提供了一个能让客户处理异常的方式。
- 析构函数不要吐出异常。如果析构函数调用的函数抛出异常,析构函数应该捕捉任何异常,然后吐下它们(不传播)或结束程序。
- 如果客户需要对某个操作函数运行期间抛出的异常做反应,class提供一个普通的函数来执行操作。
条款09.绝对不要在构造和析构函数中直接或者间接调用virtual函数
例如在一个对象创建时需要log记录。
class Transaction{
public:
Transaction();
virtual void logTransaction()const = 0; //做出一份因类型不同而不同的日志记录
...
};
Transaction::Transaction()
{
...
logTransaction(); //最后动作是记录这笔交易
}
class BuyTransaction:public Transaction{
public:
virtual void logTransaction()const; //记录(log)此型交易
...
};
class SellTransaction:public Transaction{
public:
virtual void logTransaction()const; //记录(log)此型交易
};
BuyTransaction b;
当BuyTransaction被构造是,首先会调用基类的构造函数,此时虚构函数logTransaction会调用Transaction中的纯虚函数导致错误,及时是定义过的纯虚函数也会导致一系列的问题。析构函数同理。
对于如何解决这个问题的话可以如下所示:
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(logfo); //如今是个non-virtual调用
}
class BuyTransaction:public Transaction{
public:
BuyTransaction(parameters)
:Transaction(createLogString(parmeters))
{...}
...
private:
static std::string createLogString(parameters);
};
条款10.在operator=返回一个reference to *this
为了实现对象的连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参,因此为class实现赋值操作是应该遵循:
class Widget{
public:
...
Widget& operator=(const Widget& rhs)
{
...
return* this; //返回左侧对象
}
}
同样operator+=也适用。
条款11.operator=中处理“自我赋值”
尽管自己对自己赋值看似很蠢,但是在C++中有别名的情况下还是可能会发生这种情况,举个自己给自己赋值时产生错误的情况。
class Bitmap{...};
class Widget{
...
private:
Bitmap* pb; //指针,指向一个从heap分配而得的对象
};
Widget&
Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb); //使用rhs bitmap的副本(复件)
return *this;
}
上面在自我赋值前就已经释放动态内存了。可以如下修改
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* POrig = pb; //记住原先的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的一个复件
delete pOrig; //删除原先的pb
return *this;
}
这段代码既能保持自我赋值也能具有“异常安全性”。另外一个替代方案是使用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;
}
对于参数传递方式
Widget& Widget::operator=(Widget rhs)
{
swap(rhs); //将*this的数据和复件/副本的数据交互
return *this;
}
条款12.复制对象时不要忘记每一个成分
1、自定义的COPY构造函数和赋值操作符一定要却把本类中的所有成员,包括新增加的成员和父类的所有成员都要复制过来。
2、不要尝试COPY构造函数和赋值操作符进行互相调用。应该将共同机能放进第三个private函数,并由两个coping函数共同调用。
class Date{};
class Customer{
public:
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
std::string name;
Date lastTransaction;
};
Customer::Customer(const Customer &rhs)
:name(rhs.name),
lastTransaction(rhs.lastTransaction) //复制rhs的数据
{
}
Customer &Customer::operator=(const Customer &rhs)
{
name = rhs.name;
lastTransaction = rhs.lastTransaction;
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)
{}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}