Effective c++ 2 5...12
条款005:了解C++默默编写并调用哪些函数
如果有引用或者const成员变量,编译器拒绝合成赋值操作符。
template <typename T> class NameObject{ public: NameObject(string& name,const T& value): nameValue(name),objectValue(value) { } private: string& nameValue;//&引用变量 const T objectValue; }; //warning C4512: 'NameObject<T>' : assignment operator could not be generated int main() { string newDog("Per"); string oldDog("Sat"); NameObject<int> p(newDog,2); NameObject<int> s(oldDog,36); p = s;// error C2582: 'operator =' function is unavailable in 'NameObject<T>' }
编译器不知道如何赋值不能修改的引用和const。
条款006:若不想使用编译器自动生成的函数,就该明确拒绝
抵制拷贝
1 private: 2 __CLR_OR_THIS_CALL basic_ios(const _Myt&); // not defined 3 _Myt& __CLR_OR_THIS_CALL operator=(const _Myt&); // not defined 4 5 //basic_ios类 声明了private的 拷贝构造和 赋值操作符 不定义
声明:让编译器不能合成默认的
Private:让外部不能使用
不定义:让内部和友元也崩想用
uncopyable class 基类
1 class uncopyable{ 2 protected: 3 uncopyable(){} 4 ~uncopyable(){} 5 private: 6 uncopyable(const uncopyable&); 7 uncopyable& operator=(const uncopyable&); 8 }; 9 10 class HomeForSale: private uncopyable{ 11 //HomeForSale类抵制拷贝 12 }; 13 //error C2248: 'uncopyable::uncopyable' : cannot access private member declared in class 'uncopyable'
条款007:为多态基类声明virtual析构函数
没有虚析构,基类指针指向派生类对象时,delete基类指针,会调用基类的析构函数,很有可能只析构掉基类部分,残余派生类部分。100%是未定义行为。
虚函数有4字节虚表。
如果你不打算使用多态,这东西就不用定义。
条款008:别让异常逃离析构函数
最坏也要在析构函数里catch住任何异常。
如果可能的话,最好在析构前用普通函数打扫战场。降低析构函数里发生异常的可能性。
1 class DBConnection{ 2 public: 3 static DBConnection create(); 4 void close();//断开连接的接口提供给用户了,但用户不一定会用。发生没断开链接就析构了的问题。 5 }; 6 //为确保析构前调用close()而创建的类 7 class DBConn{ 8 public: 9 void close(){//提供给客户断开连接的接口 用户可以捕捉这个接口的异常,进行处理。 10 db.close();//而不是让析构函数去调用可能发生异常的函数close 11 closed = true;//如果这里成功了,析构函数就不再调用close 12 } 13 ~DBConn() 14 { 15 if(!colsed){//如果还是沦落到要析构函数来调用可能异常的函数 16 try{db.close();} 17 catch (...) { 18 cout<<; //catch住任何异常,就可以了。不要从析构函数抛出异常。在这里吞掉。做记录或者调用abort()什么的也可以。 19 } 20 } 21 22 } 23 private: 24 DBConnection db; 25 bool closed; 26 }; 27 28 DBConn dbc(DBConnection::create());//操作dbc对象 dbc对象析构的时候保证断开连接之后再析构。
条款009:绝不在构造和析构过程中调用virtual函数
基类构造函数->派生类构造函数->…->派生类析构函数->基类析构函数。
如果基类在构造函数里调用了virtual函数,创建派生类对象的时候,先调用基类构造,此时,还没有派生类对象的时刻(此时,对象的类型就是基类类型),用的是virtual函数基类的版本(如果定义了,没定义就报连接错),你本意是创建派生类对象的,这就错了。你的派生类构造函数中还会调用这个virtual函数吗?不一定。派生类可能定义了这个virtural函数的派生类版本,但是没有在自己的构造函数中调用。
如果在基类的析构函数里调用了virtual函数,析构派生类对象的时候,先析构派生类对象,再析构基类对象,此时,派生类对象已经不存在的时刻,用的是virtual的基类的版本。
如果想构造函数里调用的函数实现多态那种效果怎么办?
这个要调用的函数不是虚函数了,那么派生类构造函数中,把派生类所拥有的特殊信息传给基类的构造函数(构造函数可以有各种形参),来展现特别性。
条款10:令operator= 返回reference *this
因为标准里的=操作符有 连锁赋值的功能。
int a=b=c=15;
那么类的重载赋值操作符也保留这个功能,依靠operator=返回自身类型的引用来实现。即
return *this;
条款11:在operator=中处理“自我赋值”
实现异常安全性 顺便实现自我赋值安全。
例如:pb成功获得正确的新对象之前不删除旧对象。这种策略就同时达到了异常安全性和自我赋值安全性。
1 Widget& Widget::operator=(const Widget& rhs) 2 { 3 Bitmap* pOrig = pb; // 记住原先的pb 4 pb = new Bitmap(*rhs.pb);//pb获得新值——源头的副本 5 delete pOrig;//删除原先的pb 6 return *this; 7 }
如果自我赋值,rhs pb pOrig都指向同一个对象。pb指向一个新的对象(拷贝),成功了,之后删除旧的。可以
如果new发生异常,抛出到异常处理部分,不再进行下面的delete rhs指向rhs的, pb指向pb的,pOrig指向pb的。
(异常抛出后 再怎么弄来着?栈展开,被catch住就处理,不能处理就退出函数,编译器保证,每个函数退出的时候,撤销在异常发生之前创建的所有对象(包括自动调用类对象的析构函数),释放函数的局部存储。栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。)
调用new分配的资源,因异常退出函数时,编译器不会自动释放,不会自动删除该指针。
又例如:copy and swap技术
利用拷贝构造,swap函数。
1 class Widget{ 2 ... 3 void swap(Widget& rhs); 4 ... 5 }; 6 7 Widget& Widget::operator=(const Widget& rhs) 8 { 9 Widget temp(rhs); 10 swap(temp); 11 return *this; 12 }
利用pass-by-value 传了rhs的副本(还是调用了拷贝构造 构造了temp 只不过叫rhs罢了)
class Widget{ ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(Widget rhs) { swap(rhs); return *this; }
条款12:复制对象时,复制它的每一个部分
忘了拷贝构造【类类型】成员……
忘了拷贝构造【基类的部分】什么的……会调用基类的默认构造,和你的拷贝源对象的基类部分很可能大相径庭了……
为派生类写copying函数的时候,必须复制其基类部分。往往是private成分,所以考虑用派生类的copying函数调用基类的拷贝构造,调用基类的赋值操作符函数 哦呵呵。
不该令copying assignment操作符调用copy构造函数。能调copy assignment的时候,你的对象显然已经存在了,你还调构造函数,这不是在构造已经存在的对象么…消除重复代码的方法是,建一个新的成员函数给两者调用,这样的函数往往是private且明明为init。