《Effective C++》读书笔记(三)
《Effective C++》读书笔记(三)
《 Effective C++ 改善程序与设计的 55 个具体做法》条款 08 ~ 条款 12
在构造和析构期间不要调用 virtual 函数
假如你在基类的构造函数中调用了一个虚函数,然后当你在建立一个派生类对象时,会发现它调用的那个虚函数并不是你想要的那个版本
因为在派生类的构造函数调用之前,基类的构造函数一定会被更早的调用,然后在基类构造期间,派生类的成员变量并未初始化,你所建立的对象类型暂时是基类的而不是派生类的,如果使用运行期类型信息,也会把对象视为基类类型,所以调用的那个虚函数也就不会下降到派生类阶层,对象在派生类构造函数执行前不会成为一个派生类对象。析构函数也是同理
所以要想解决这个问题,可以令派生类将必要的构造信息向上传递至基类构造函数,如:
class Transaction
{
public:
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)) // 将log信息传给基类构造函数
{
// ...
}
private:
static std::string createLogString(parameters);
};
其中 createLogString 函数是静态的,也就不可能意外地指向尚未初始化的成员变量
std::abort()
中止当前进程,产生程序终止的异常。
没有返回值,而且程序终止时不会破坏任何对象,不会导致数据竞争。
如果一个程序遇到”于析构期间发生的错误”后无法继续执行,就可以制作运转记录,记下这个失败,然后调用 std::abort()
结束程序,这样可以阻止异常从析构函数传播出去。因为从析构函数中抛出异常是危险的,如果抛出发生在堆栈展开操作期间(如另一个异常已经在传播),程序通常会崩溃;如果子类析构函数抛出异常,则不会调用父析构函数,会使对象处于部分销毁状态。所以如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。
确保 operator= 能正确处理自我赋值
下面是自我赋值时并不安全的实现代码:
class Bitmap {};
class Widget
{
private:
Bitmap *pb;
};
Widget &Widget::operator=(const Widget &rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
假如 *this 和 rhs 是同一个对象时, delete 销毁操作之后,*rhs.pb
所指向的就是一个已被删除的对象
所以如果想解决这个问题,可以先进行”证同测试 (identity test) “,如果是自我赋值则不做任何事情。或者可以精心安排一下语句顺序,记住原先的 pb ,然后先复制再删除
可以使用 copy and swap 技术:
class Widget
{
// ...
void swap(Widget &rhs); // 交换*this和rhs的数据
};
Widget &Widget::operator=(const Widget &rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
Subscribe to bbbiggest's blog
Get the latest posts delivered right to your inbox