class Empty{
};
class Empty{
public:
Empty(){
}
~Empty(){
}
Empty(const Empty& rhs){
}
Empty& operator=(const Empty& rhs){
}
};
template <class T>
class NamedObject{
public:
//不再接收一个const名称,因为nameValue现在是一个 reference-to-non-const string
NamedObeject(std::string& name,const T& value);
...
private:
std::string& nameValue;
const T objectValue;
}
std::string newDog("Persephone");
std::string oldDog("Stach");
NamedObject<int>p(newDog,2);
NamedObject<int>s(oldDog,36);
p=s;
//p对象中的引用会指向s中的引用吗?c++并不允许引用改指不同的对象,编译器拒绝编译
解决1:因为所有编译器产出的函数都是public,所以我们直接将copy函数和copy assignment声明为private成员,阻止编译器创建自己的版本。问题:member函数和friend函数拷贝这个对象那么就会出问题。
解决2:同样是声明在private中,但是用一个专门为了阻止copying动作而设计的base class。为了阻止一个类对象被拷贝,可以继承这一个bass class。
class Uncopyable{
protected:
Uncopyable(){
}
~Uncopyable(){
}//允许derived对象构造和析构
private:
Uncopyable(const Uncopyable&);//但阻止copy
Uncopyable& operator=(const Uncopyable&);
}
class HomeForSale:private Uncopyable{
...
}
==总结:==为了驳回编译器自动提供的机能,可以将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。
设计一个TimeKeeper base class和一些derived classes作为不同的计时方法:
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
...
}
class AtomicClock:public TimeKeeper{
};
class WaterClock:public TimeKeeper{
};
class WristWatch:public TimeKeeper{
};
可以设计Factory函数,返回指针指向一个计时对象。
TimeKeeper* getTimeKeeper();//返回一个指针,指向派生类的动态分配对象
TimeKeeper* ptk=getTimeKeeper();//从基类继承体系,获得一个动态分配对象
...//运用它
delete ptk;//释放它避免资源泄露
注意!!!
getTimeKeeper返回的指针指向派生类,但是删除这个类却通过一个基类指针,而这个基类指针只有non-virtual析构函数。这就导致了delete后调用基类的析构函数来析构派生类,那么结果就是只能析构派生类中bass class成分,而drived class的部分则得不到释放,导致内存泄漏。
解决方法
给bass class一个virtual析构函数。
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
...
}
TimeKeeper* ptk=getTimeKeeper();
...//运用它
delete ptk;//释放它避免资源泄露
任何class只要带有virtual函数几乎确定也应该拥有一个virtual析构函数。反之,如果一个class没有virtual函数,通常表示这个class不被企图当做base class,令其析构函数为virtual往往是一个馊主意。实现virtual往往需要vptr(指向一个由函数指针构成的数组)即vtbl,增加对象大小达到50%~100%。
有时候令class带上一个pure virtual析构函数会颇为便利,导致abstract classes(不能被实体化)==必须为这个pure virtual析构函数提供一份定义。
class AWOV{
public:
virtual ~AWOV()=0;
}
AWOV::~AWOV(){
}
总结
1. polymorphic(带有多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,那么他就应该拥有一个virtual析构函数。
2. classes的设计目的如果不是为了作为一个base classes使用,或者不是为了具备多态性,就不该声明virtual析构函数。
问题
假设一个class中的析构函数会吐出异常,且定义一个容器实例v存放这个class的许多实例,当销毁这个v时就会调用多次析构函数,如果抛出多个异常程序可能会过早结束或者出现不明确行为。
class DBConnection{
public:
...
static DBConnection create();//返回对象
void close();//关闭联机,失败则抛出异常
}
为了使用户不忘记在DBconnection中调用close,一个合理的想法就是创建一个用来管理DBconnection的class,在这个class的析构函数中调用DBConnection::close()函数。class DBConn{
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection db;
}
//这样客户就可以写出这样的代码
{
//开启一个区块。
DBConn dbc(DBConnection::create());//建立DBConnection对象并交给DBConn对象以便管理,通过dbc结构管理DBConnection对象,在区块结束的时候DBConn自动销毁,因而自动为DBConnection对象调用close
...
}
如果:调用close函数异常那么DBConn的析构函数就会传播这个异常,允许异常抛出析构函数。DBConn::~DEConn()
{
try(db.close();)
catch(...){
std::abort();
}
}
DBConn::~DEConn()
{
try(db.close();)
catch(...){
}
}
以上两种方法都无法对“导致close抛出异常”的情况做出反应。class DBConn{
public:
void close()//供客户使用的新函数
{
db.close();
closed=true;
}
~DBConn()
{
if(!closed)
{
try(db.close())//关闭连接(如果没有调用close的话)
catch(...){
//制作运转记录,记录下对close的调用失败;
...
}
}
}
private:
DBConnection db;
bool closed;
}
总结
例子
假设你有一个class继承体系,用来模拟股市交易,买入和卖出一定要有审计,每当创建一个交易对象在审计日记中都需要有记录。
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;
...
}
现在,当执行以下操作时会发生什么事情?
BuyTransaction b;
编译器不总能够检测出构造函数或者析构函数是否调用virtual函数:
如果Transcation有多个构造函数,每个都需要执行某些相同的工作,那么避免将代码重复的优秀做法就是把共同的初始化代码(其中包括对logTransaction的调用)放进一个初始化函数中。
class Transaction{
public:
Transaction()
{
init();}//调用non-virtual
virtual void logTransaction() const = 0;
...
private:
void init()
{
...
logTransaction();
}
};
上述方法会让编译器和连接器没有任何报警行为。但运行时会调用纯虚函数导致系统中止程序。
问题
那么如何保证每次有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用呢?
解决方法
一种做法是将这个纯虚函数改为non_virtual函数,然后要求派生类的构造函数传递必要的信息给基类构造函数。而后那个构造函数就可以安全的调用logTransaction了
class Transaction{
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const;
...
}
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo);
}
class BuyTransaction{
public:
BuyTransaction(parameters):Transaction(createLogString(parameters))//将log信息传递给base class构造函数
{
...}
...
private:
static std::string createLogString(parameters);
}
总结
你无法使用virtual函数从基类向下调用,但是在构造期间你可以用派生类将必要的构造信息向上传递至基类的构造函数。但是要注意,必要的构造信息由一个静态成员函数提供,静态成员函数保证了传入的不会是尚未初始化的成员变量
对于所有的赋值相关运算,都可以返回一个绑定在*this上的引用。
class Widget{
public:
...
Widget& operator=(const Widget&rhs)
{
...
return *this;
}
};
问题
当对象被赋值给自己的时候,自我赋值就发生了。
class Widget{
...};
Widget w;
...
w=w;//看起来很显而易见的错误
a[i]=a[j];
*px=*py;/
class Base{
...};
class Derived:public Base{
...};
void doSomething(const Base&rb,Derived* pd);//基类引用或指针都可以指向一个Derived class对象。
自我赋值时可能会发生“在停止使用资源前意外释放了它”的陷阱。
假设你建立了一个指针指向一块动态分配的位图(bitmap):
class Bitmap{
...};
class Widget(
...
private:
Bitmap *pb;
};
自我赋值时并不安全的代码
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb=new Bitmap(*rhs.pb);
return *this;
}
避免这种错误,传统做法是在代码最前面加上一个证同测试达到自我赋值的检验目的
这种方法可以保证自我赋值安全性,但还不能保证异常安全性(分配时的内存不粗或者是Bitmap的copy构造函数发生异常)
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地址
pb=new Bitmap(*rhs.pb);//令pb指向*pb的一个副本 会出现异常的地方
delete pOrig;//释放以前的pb地址
return *this;
}
第三种方案的替代方案“copy and swap”技术
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);//为rhs做一份数据复件
swap(temp);//交换temp和*this的数据,temp会在函数结束之后自动释放内存
return *this;
}
copy and swap技术的另一种实现基于以下两点:
(1)某class的copy assignment操作符可能会被声明为by value方式
(2)以by value方式传递值会造成一份拷贝
基于以上两点我们写下另一种实现:
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
牺牲了清晰性,但是将copy动作从函数本体挪到函数参数构造阶段却可令编译器有时生成更高效的代码。
如果你自己写出copy函数,那么在你添加新的成员变量的时候编译器不会提醒你。
一旦发生继承,那么派生类构造阶段调用基类构造函数时就会被不带实参的缺省构造函数替代,而你自己的那个版本就会失效。
确保(1)复制所有的local成员变量。(2)调用所有base classes内的适当的copying函数。