Effective C++ 笔记(2)构造/析构/赋值运算
05、了解C++默默编写并调用哪些函数
(1)、默认构造函数
Empty(){...} //调用父类构造函数,non-static成员变量构造函数,不会默认初始化内置类型
(2)、析构函数
~Empty(){...} //调用父类析构函数,non-static成员变量析构函数
(3)、拷贝构造函数
Empty(const Empty& rhs){...} //单纯地将来源对象的每一个non-static变量拷贝到目标对象
(4)、赋值构造函数
Empty& operator=(const Empty& rhs){...} //单纯地将来源对象的每一个non-static变量拷贝到目标对象
当类内有引用、const、及父类的拷贝构造函数,赋值构造函数为private时,编译器会拒绝生成这一类函数。换言之,如果类内有引用、const成员变量,或者其父类的相关函数不可访问时,必须手动生成。有指针类型变量时,存在“深拷贝、浅拷贝”问题!!!
class Empty: { public: Empty(){...} Empty(const Empty& rhs){...} ~Empty(){...} Empty& operator=(const Empty& rhs){...} private: const int m_cInt; //只读成员变量 int &ref; //引用型成员变量,必须在每个构造函数手动初始化 };
06、若不想使用编译器自动生成的函数,就该明确拒绝
禁止被拷贝构造或赋值构造的做法:
(1)、将相应成员函数声明为private且不去实现它。(链接期出错)
class HomeForSale { public: HomeForSale(); ~HomeForSale(); private: HomeForSale(const HomeForSale& rhs); //只有声明 HomeForSale& operator=(const HomeForSale& rhs); };
(2)、private继承Uncopyable类(编译期出错)
class Uncopyable { public: Uncopyable(){}; ~Uncopyable(){}; private: Uncopyable(const Uncopyable& rhs); Uncopyable& operator=(const Uncopyable& rhs); };
07、为多态基类声明virtual析构函数
(1)、polymorphic(带多态性质的)base classes应该声明一个virtual析构函数,这样通过delete 基类指针时,也会调用其指向对象的析构函数。避免内存泄漏。如果一个class带有一个或多个virtual函数,它就应该拥有一个virtual析构函数。
(2)、classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不应该声明virtual函数。(会多出一个vptr指针,占用内存),string及STL容器均为无virtual函数!尽量别继承它们做事。
引申:c++11中可以override,final关键字指定。只能作用于虚函数
override,表示此虚函数必定“重写”了基类中的对应虚函数。
final,(1)作用在虚函数:表示此虚函数已处在“最终”状态,后代类必定不能重写这个虚函数。
(2)作用在类:表示此类必定不能被继承
编译器将帮你检查是否“必定”
08、别让异常逃离析构函数
(1)、析构函数绝对不要抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后处理它们,或者结束程序;
(2)、如果接口使用者需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作;
class DBConn { public: void close() { db.close()//可能抛出异常函数 closed = true; } ~DBConn() { if (!closed) //如果客户没有主动关闭的话 { try { db.close(); } catch(...) { //制作运转记录,记下对close的调用失败 } /* code */ } } private: DBConnection db; bool closed; };
09、不要在构造和析构过程中调用virtual函数
class Transaction //基类 { public: Transaction(); ~Transaction(); virtual void logTransaction const = 0; }; Transaction::Transaction() { ... logTransaction();//记录这笔交易 } class BuyTransaction : public Transaction { public: BuyTransaction(); ~BuyTransaction(); virtual void logTransaction()const override; //记录这笔交易 };
BuyTransaction b;时先执行Transaction::Transaction,此时传入的this指针为Transaction* 故调用的是Transaction::logTransaction(),此时BuyTransaction还没被构造出来。
构造函数:base::base-->derive::derive
析构函数:base::~base-->derive::~derive
10、令operator=返回一个reference to *this
用于链式赋值
11、在operator= 中处理“自我赋值”
推荐做法:
Widget& Widget::operator=(Widget rhs) { swap(rhs); //成员函数,进行交换 return *this; }
(1)、确保当对象进行自我赋值时operator=有良好的行为。包括考虑“来源对象”和“目标对象”的地址(是否为同一个)、精心周到的语句顺序、以及复制交换;
(2)、当一个函数操作多个对象时,确保即使这些对象为同一个对象,其行为仍然正确。
12、复制对象时勿忘其每一个成分
(1)、Copying函数应该确保复制“对象内的所有成员变量”及“所有base class”成分;
(2)、不要用某个copying函数实现另一个copying函数。应该将共同的代码放进第三个函数中,并由两个copying函数共同调用。