条款六:不想使用编译器默认提供的函数时的做法
对于一个HomeForSale
类来说,如果你不希望有拷贝构造操作和赋值操作,可以将他们声明为private
1 2 3 4 5 6 7 8
| class HomeForSale { public: ... private: HomeForSale(const HomeForSale&); HomeForSale& operator=(const HomeForSale&); };
|
但是这样做在编译期只能对用户的赋值和拷贝构造报错,成员函数和友元函数调用了这样的操作时会在链接期报错。想把报错都移动到编译期时,可以构建一个uncopyable 类,并继承它。
1 2 3 4 5 6 7 8 9
| class Uncopyable { protected: Uncopyable(); ~Uncopyable(); private: Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); };
|
- 为了禁止编译器默认提供的函数,可以将其声明为private,或者构建一个uncopyable类并继承。(使用boost库提供的noncopyable也可以
条款七:为多态基类声明virtual析构函数
多态基类是指:一个基类设计的目的是”通过基类接口处理派生类对象“。
如下面的TimeKeeper
类:
1 2 3 4 5 6 7 8 9
| class TimeKeeper{ public: TimeKeeper(); ~TimeKeeper(); };
class AtomicClock : public TimeKeeper(){...}; class WaterClock : public TimeKeeper(){...}; class WristWatch : public TimeKeeper(){...};
|
当定义一个工厂函数TimeKeeper* getTimeKeeper()
返回三个派生类之一时
即TimeKeeper* ptk = getTimeKeeper()
用delete ptk
只会对其中的基类部分进行销毁,派生的都有数据不会被销毁。这时需要将Timekeeper
中的析构函数声明为虚函数才能解决这个问题。
1 2 3 4 5
| class TimeKeeper{ public: TimeKeeper(); virtual ~TimeKeeper(); };
|
- virtual析构函数只有像
TimeKeeper
例子中的情况下才使用。
条款八:别让异常逃离析构函数
这条条款主要目的是告诫我们不能让析构函数抛出异常。
1 2 3 4 5 6 7 8 9
| class Widget{ public: ... ~Widget( ) {...} }; void doSomthing() { std::vector<Widget> v; }
|
若~Widget()
v内第一个元素抛出异常,则后续的元素也会抛出异常,当多个异常被抛出,程序的执行就不收我们控制,这是很可怕的现象。对于一个析构函数必须执行可能抛出异常的类,可以再创建一个类对其进行管理,并吞下异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class DBConnection{ public: ... static DBConnection create(); void close(); };
class DBConn{ public: ... ~DBConn() { try{ _db.close(); } catch(...){ 将调用close()失败的信息记入日志 std::abort(); } }
private: DBConnection _db; };
|
但上面两种做法使得close()
异常的出现对于用户来说几乎是透明的,用户无法对其进行处理,正确做法应该是下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class DBConn{ public: ... void close() { _db.close(); _closed = true; } ~DBConn() { if(!_closed) { try { _db.close(); } catch(...){ 上面两种的一种 } } } private: DBConnection _db; bool _closed; };
|
这一代码给了用户调用_db.close()
并处理异常的机会。如果用户没有调用该函数,则析构时也会默认调用,清理资源,并且是相对安全的。
- 析构函数不能抛出异常。
- 提供普通函数使用户可以处理操作可能会抛出的异常。
条款九:不要在构造和析构时调用虚函数
如果基类构造函数中有虚函数,在派生类构建时,会调用基类的版本而不会下降到派生类(析构函数同理)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Transaction{ public: Transaction() { init(); } virtual void logTransaction() const = 0; private: void init() { ... logTransaction(); } };
|
这段代码调用虚函数方式比较隐蔽,他会造成上述问题。为了解决在构造函数中调用logTransaction()
,并且在其派生类中实现派生类独有形式的操作,可以将其改为非虚函数形式,每次传递信息给Transaction
构造函数,然后就可以安全调用logTransaction()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 Transaction{ public: BuyTransaction(parameters) : Transaction(createLogString(parameters)) {} private: static std::string createLogString(parameters); };
|
注意在BuyTransaction
中的private static
函数,是很有必要的,静态函数可以避免使用未初始化的成员变量(?这个没搞太懂)。
- 构造和析构函数中不能调用虚函数,因为其不会下降到派生类,只执行基类的定义。
条款十:是operator=返回一个*this的引用
在出现连续赋值,如x = y = z = 15
时,赋值操作符必须返回*this的引用。这相当于规定,一定要遵守他。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Widget{ public: Widget& operator=(const Widget &rhs) { ... return *this; } Widget& operator+=(const Widget &rhs) { ... return *this; } Widget& operator-=(const Widget &rhs) { ... return *this; } };
|