《Effective C++》读书笔记 被你忽略的关于构造析构赋值
如果程序员没有定义,那么编译器会默认隐式为你创建一个copy构造函数,一个copy赋值操作符,一个析构函数。另外如果你没有声明任何构造函数,编译器会为你声明一个default构造函数。
但是只有当这些函数被用到时,他们才会被创建。例如Empty a(b),会创建copy构造函数。
- class Empty
- {
- Empty(){...}//default构造函数,如果没有声明任何构造函数,编译器会自动声明
- Empty(const Empty& rhs){...}//copy构造函数,如果没有声明,编译器自动声明
- ~Empty(){}//析构函数,同copy构造函数
- Empty& operator=(const Empty& rhs){...}//copy复制操作符,同上
- };
对于copy赋值操作符,有一个需要注意的地方。考虑这样一个类NamedObject,包含一个string& nameValue和一个const int objectValue 。假设你使用编译器为你创建的copy赋值操作符,看看会发生什么:
- string newDog("Persephone");
- string oldDog("Satch");
- NamedObject p(newDog,2);
- NamedObject s(oldDog,36);
- p=s;//将s拷贝赋值到p
p的reference对象nameValue被重新赋值了!我们知道reference对象是不能变动的,因此这总是错误的。同样p的const int objectValue也被改变。
因此如果想在一个内含reference或const成员的类内支持赋值操作,你必须定义自己的copy赋值操作符。
如果你不使用copy构造函数或者copy赋值操作符,你应该将他们声明为private并且不定义它。这样可以防止友元函数或成员函数或派生类使用它。
为多态基类声明virtual析构函数
为防止派生类在销毁时发生错误,即在delete派生类时只有基类部分被销毁,属于派生类的地方却还在,你应该将基类的析构函数声明为virtual。
同时,无端将所有类的析构函数声明为virtual是个错误的习惯,因为virtual会增加对象体积,造成不必要的资源浪费。一个心得是,在类内至少包含一个virtual函数时才为他声明virtual析构函数。
有时候你希望拥有一个抽象类,但是却没有纯虚函数,你可以为它声明一个纯虚析构函数,这样那个类就成为了抽象类。
绝不在构造和析构过程中调用virtual函数
当一个派生类调用构造函数时,若其调用了virtual函数,virtual函数调用的版本为其基类的版本,这不是我们希望看到的。这是因为在派生类对象的基类构造期间,对象的类型是基类而不是派生类。不只virtual函数会被编译器解析至基类,若使用运行期类型信息(dynamic_cast和typeid),也会把对象视为基类。
但是如果你想确保每次一个(继承体系上的)类的对象被创建,就会有适当版本的对象函数(多态函数)被调用,也就是在初始化的过程中调用。你可以将该函数声明为非virtual的,然后每次给他传递必要信息。即如下形式:
- class Transaction
- {
- public:
- explicit Transaction(constant string& logInfo)
- {
- logTransaction(logInfo);//调用logTransaction
- }
- void logTransaction(logInfo) const;//声明为非virtual
- };
- class BuyTransaction:public Transaction
- {
- public:
- BuyTransaction(parameters):Transaction(parameters){}//每个BuyTransaction都有专属自己的logTransaction信息
- };
关于拷贝赋值操作符,有两个编程好习惯
- 令operator=返回一个reference to *this
- 在operator=中处理“自我赋值”
- int x,y,z;
- x=y=z=1;
上面的代码能通过编译并且表现得完全符合我们的预期。为了使我们的代码和C++规范保持一致,我们也应该令operator=返回一个对*this的引用。当然你不这样做编译器也不会报错,只是这是一个共同遵守的约定。
某些情况下,会出现一种令我们意想不到的情况:对象自我赋值,如果你够聪明,你当然不会写出这样的代码,但是你不能保证使用你的代码的人也像你一样聪明,所以,这种情况应该被我们考虑到。
看看一个不安全的operator=的实现,看看会发生什么:
- Widget& Widget::operator=(cosnt Widget& rhs)//widget类中包含了一个Bitmap类的成员pb
- {
- delete pb;
- pb= new Bitmap(*rhs.pb);
- return *this;
- }
上面的代码一般人当然会这么实现,但是看看当被自我赋值的时候会发生什么。它先删除了自己的pb对象,然后又将pb赋值给它自己,但是我们知道它已经被删除了,这是很明显的一个错误。
一个很好的解决方法是,以下的实现方式:
- Widget& Widget::operator(cosnt Widget& rhs)
- {
- Bitmap* pOrig = pb;
- pb = new Bitmap(*ths.pb);
- delete pOrig;
- return *this;
- }
拷贝函数应该确保复制对象内的所有成员变量及所有基类成分,因此你应该为派生类撰写拷贝构造函数,因为如果不这样做,派生类的default构造函数会对基类进行default构造,也就是说,派生类的基类部分没有被拷贝,这显然是不符合我们期望的。所以,你应该为派生类撰写拷贝函数。
上一篇: 浅谈python的深浅拷贝