4.1 条款5:了解C++默认编写并调用哪些函数(Know what functions C++ silently writes and calls)
了解C++默默编写并调用哪些函数:
如果写下:
class Empty
{
};
编译器会产生如下:
class Empty
{
public:
Empty(){…} //default构造函数
Empty(const Empty &rhs){…} //copy构造函数
~Empty(){…} //析构函数
Empty & operator=(const Empty &rhs){…} //copy assignment操作符
};
示例4-1-1默认产生的函数
唯有当这些函数被调用(被调用),它们才会被编译器创建出来。
Empty e1; //default构造函数
//析构造函数
Empty e2(e1); //copy构造函数
e2 = e1; //copy assignment操作符
注意,编译器产生的析构函数是个是个non-virtual函数。
4.2 条款6:若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of compiler-generated function you do not want)
若不想使用编译器自动生成的函数,就该明确拒绝。
所有编译器产生出的函数都是public。
为了不让编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。
如果我们不想将copy构造函数或copy assignment操作符声明为private,就阻止了编译器暗自创建其专属版本;而令这些函数为private,是你得以成功阻止人们调用它。但是member函数和friend函数还是可以调用你的private函数,为了阻止这个,可以不去定义它们。
class CHomeForSale
{
public:
private:
CHomeForSale(const CHomeForSale&);
//只有声明
CHomeForSale & operator=(const CHomeForSale&);
};
示例4-2-1 copy函数和操作符声明为私有
有了上述class定义,当客户企图拷贝CHomeForSale对象,编译器会阻扰他。如果你不慎在member成员函数或friend函数拷贝对象,就轮到连接器发出抱怨。更好的做法是:
class CUncopyable
{
public:
protected:
//允许派生类构造和析构
CUncopyable();
~CUncopyable();
private:
CUncopyable (const CUncopyable &);
//只有声明
CUncopyable & operator=(const CUncopyable &);
};
class CHomeForSale
:private CUncopyable
{ //不再声明copy构造函数和copy操作符
。。。。。。
};
示例4-2-2 阻止copying动作而设计的base class类
4.3 条款7:为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base class)
为多态基类声明virtual析构函数。
带多态性质的(polymorphic)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,他就是应该拥有一个virtual析构函数。
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明为virtual析构函数。
class CTimeKeeper
{
public:
CTimeKeeper();
virtual ~CTimeKeeper();
…
};
class CAtomicClock : public CTimeKeeper{…};
class CWaterClock : public CTimeKeeper{…};
class CWristWatch: public CTimeKeeper{…};
void main(void)
{
CAtomicClock *pac = new CAtomicClock;
Delete pac;
//如果base不是虚析构函数,则base资源没有释放,造成资源泄露
}
示例4-3-1 base类声明虚析构函数
4.4 条款8:别让异常逃离析构函数(Prevent exception from leaving destructors)
别让异常逃离析构函数。
假设我们需要封装对数据库的操作,在析构函数需要关闭数据库连接。
class CMysqlIface
{
public:
~ CMysqlIface()
//确保数据库连接总是被关闭
{
m_conn ->close();
}
private:
sql::Connection *m_conn;
};
示例4-4-1 析构函数无异常处理
在上述代码中,如果析构时出现异常,就会造成麻烦,且是难以驾驭的麻烦。
所以,析构函数绝不能吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
class CMysqlIface
{
public:
~ CMysqlIface() //确保数据库连接总是被关闭
{
try
{
m_conn ->close();
catch( ...)
{ //制作运作记录,记下对close的调用失败
std::abort();
}
}
private:
sql::Connection *m_conn;
};
class CMysqlIface
{
public:
~ CMysqlIface() //确保数据库连接总是被关闭
{
try
{
m_conn ->close();
catch( ...)
{ //制作运作记录,记下对close的调用失败
}
}
private:
sql::Connection *m_conn;
};
示例4-4-2 析构函数不传播异常或终止程序
一般而言,将异常吞掉是个坏主意,因为他压制了某些动作失败的重要信息。最好的做法是:
class CMysqlIface
{
public:
void close()
//供客户使用
{
m_conn ->close();
closed = true;
}
~ CMysqlIface() //确保数据库连接总是被关闭
{
try
{
if (!closed)
m_conn ->close();
catch( ...)
{ //制作运作记录,记下对close的调用失败
}
}
private:
sql::Connection *m_conn;
bool closed;
};
示例4-4-3 析构函数吞并异常和提供客户接口
如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个接口执行该操作。
4.5 条款9:绝不在构造和析构函数过程中调用virtual函数(Never call virtual function during construction or destruction)
绝不在构造和析构函数过程中调用virtual函数,而他们调用的所有函数也都要服从统一约束。因为这类调用从不下降到derived class。
原因见书《Effective C++》第三版 P48。
4.6 条款10:令operator=返回一个refernce to *this (Have assignment operators return a reference to *this)
令operator=返回一个refernce to *this。
令赋值操作符返回一个reference to *this,来实现连锁赋值形式。
class Widget
{
public:
Widget& operator=(const Widget * rhs)
//返回类型是个reference,指向当前对象
{
…
return *this;
}
};
Widget x,y,z;
x=y=z;
//连锁赋值
示例4-6-1 连锁赋值需要操作符返回一个reference to *this
这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,比如operator+=。
4.7 条款11:在operator=中处理“自我赋值”(Handle assignment to self in operator=)
在operator=中处理“自我赋值”。
显示自我赋值:
Widget w;
w = w;
潜在的自我赋值(下列有可能是同一个对象):
a[i] = a[j];
*px = *py;
class Bitmap{…};
class Widget
{
…
private:
Bitmap * pb;
};
Widget& Widget::operator=(const Widget & rhs)
//一份不安全的operator=实现
{
delete pb; //停止使用当前的bitmap
pb = new Bitmap (*rhc.pb); //使用rhs的副本
return * this; //见条款10
}
示例4-7-1 一份不安全的operator=实现
如果rhs.pb和pb是同一个对象,上述代码就返回了一个指针指向一个已经被删除的对象。
Widget& Widget::operator=(const Widget & rhs)
//一份安全的operator=实现
{
if (this == &rhs) return *this; //证同测试
delete pb; //停止使用当前的bitmap
pb = new Bitmap (*rhc.pb); //使用rhs的副本
return * this; //见条款10
}
示例4-7-2 一份自我赋值安全的operator=实现
上述代码,自我赋值安全了,但是new Bitmap可能失败。
Widget& Widget::operator=(const Widget & rhs)
//一份异常安全的operator=实现
{
Bitmap
*pOrig = pb;
//记住原先的pb
pb = new Bitmap (*rhc.pb); //使用rhs的副本
delete
pOrig; //删除原先的pb
return * this; //见条款10
}
示例4-7-3 一份异常安全的operator=实现
这段代码还是能处理自我赋值,因为对原先的bitmap做了一份复件,删除原bitmap,然后指向新制造的那个复件。
更加高效的做法(copy and swap技术)是:
class Widget
{
…
void swap(Widget & rhs); //交换*this和rhs的数据,详见条款29
…
};
Widget& Widget::operator=(const Widget & rhs)
//一份安全且高效的operator=实现
{
Widget temp(rhs); //制作复件
swap (temp); //交换
return * this; //见条款10
}
示例4-7-4 一份异常安全且高效的operator=实现
因此,确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
4.8 条款12:复制对象时无妄其每一个成分(Copy all parts of an object)
复制对象时无妄其每一个成分。
如果你为class添加一个成员变量,你必须同时修改copying函数(copy构造函数和copy操作符)。
void logCall(const std::string &funcName); //制作一个log entry
class Customer
{
public:
…
Customer(const Customer &rhs);
Customer & operator=(const Customer &rhs);
…
private:
std::string name;
};
Customer:: Customer(const Customer & rhs):name(rhs.name)
{
logCall("Customer copy constructor");
}
Customer & Customer::operator=(const Customer & rhs)
{
logCall("Customer copy assignment operator");
name=rhs.name;
return *this;
}
示例4-8-1 copying函数实现
如果增加成员变量,相应的copying函数也要做相应的修改。
class Date{…};
//日期
class Customer
{
public:
…
private:
std::string name;
Date lastTransaction;
};
Customer:: Customer(const Customer & rhs):name(rhs.name),
lastTransaction(rhs. lastTransaction)
{
logCall("Customer copy constructor");
}
Customer & Customer::operator=(const Customer & rhs)
{
logCall("Customer copy assignment operator");
name = rhs.name;
lastTransaction = rhs. lastTransaction;
return *this;
}
示例4-8-2 增加成员变量,copying相应修改
如果发生了继承,更需要注意copying函数的编写。
class
PriorityCustomer:public PCustomer
{
public:
PriorityCustomer(const PriorityCustomer &rhs);
PriorityCustomer& operator=(const PriorityCustomer &rhs);
…
private:
int priority;
};
PriorityCustomer:: PriorityCustomer (const PriorityCustomer & rhs)
:
Customer(rhs) //调用base class的copy构造函数
, priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer & PriorityCustomer::operator=(const PriorityCustomer & rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=( rhs); //对base class成分进行赋值操作。
priority = rhs.priority;
return *this;
}
示例4-8-3 继承引起copying的修改
Copying函数应该确保复制“对象内的所有成员变量“及”所有base class成分“。不要尝试以某个copying函数实现另个copying函数。应该将共同机能放进第三个函数韩总,并由两个copying函数共同调用。