您的位置:首页 > 编程语言 > C语言/C++

关于使用析构函数的几点注意事项

2014-07-31 17:25 459 查看
        析构函数为你提供了一种机制,可以让你在系统释放对象前做一些善后工作,如清理内存,释放空间等。但是在以下两个方面使用虚构函数时要尤为注意。

        1.  多态性

        具有多态性质的base classes的析构函数应该为virture,或者说任何带有virture函数的class都应该有virture析构函数。考虑下面的代码:

class A{
A();
~A();
......
};

class B: public A{
......
};

int main(){
B b;
A* p = &b;
......
delete p;
}
      
 如果以上代码出现在你的项目中,那么就会造成灾难性的后果。在上述代码中,B类的对象经由一个base class(A类)指针被删除,但是base class(A类)有个non-virture析构函数,这就导致其内的B类成分没有被销毁(声明于B类的成员变量),然而其base class成分(也就是A类这一部分)通常会被销毁,于是导致一个诡异的“局部销毁”对象。资源泄露、败坏数据结构等问题都会因此而产生。

        消除这个问题很简单,只要给base class加上一个virture析构函数即可。之后删除derived class对象就会如你想象的那样销毁整个对象,包括所有的derived class部分。但是如果class中没有virture函数,不打算被当做base class,那么将析构函数声明为virture并不是一件好事。原因是如果class中有virture函数,那么class就会包含一个vptr来指向vtbl,这会导致class的体积增加。因此该类也不会和其他语言内的相同声明有着一样的结构,除非你明确补偿vptr,否则其不再具有移植性。对此我们只要记住:只有当class内含有至少一个virture函数,才为它声明virture析构函数。此外,如果你企图继承一个标准容器或是任何其他带有“non-virture析构函数”的class,拒绝诱惑吧!

        2.  异常

        不要让异常逃出析构函数。如果析构函数吐出异常,那么程序有可能出现不明行为或过早的结束。因此我们要谨记如果析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常然后吞下它们或结束程序。如果客户需要对某个函数运行期间抛出的异常进行反应,那么class应该提供一个普通函数(而非析构函数)来执行该操作。

        3. 虚函数

        不要在构造函数和析构函数内调用virture函数,否则会给你带来意料不到的结果。下面的例子可以说明问题:

class Transaction{
public:
Transaction();
virture void logTransaction() const;
};

Transaction::Transaction(){
......
logTransaction();
}

class BuyTransaction: public Transaction{
virture void logTransaction() const;
......
};
BuyTransaction b;

         毫无疑问,BuyTransaction的构造函数会被调用。但是在这之前,Transaction的构造函数会被更早被调用。derived class内的base class成分会在derived class自身成分被构造之前先构造妥当。Transaction的构造函数在最后一行调用virture函数logTransaction,这也是引起问题的地方。因为它调用的是Transaction内的版本而不是BuyTransaction版本,即使当前建立的对象类型是BuyTransaction。在base
class构造期间,virture函数不会下降到derived class阶层。

        以上问题的根本原因是:在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不止virture函数会被编译器解析至base class,若使用运行期类型信息,也会把对象视为base class类型。

        相同的道理也适用于析构函数。一旦derived class的析构函数开始执行,对象内的derived class的成员变量便呈现出未定义值。所以C++视它们仿佛不存在。进入base class析构函数后,对象就成为一个base class对象,而C++得任何部分,包括virture函数、dynamic_casts等等也就那么看它。还要注意的是,我们不仅要确定构造函数和析构函数没有调用virture函数,也要保证它们所调用的函数也要服从这一约定。

        而我们要怎样解决上面的问题呢?一种方法是将Transaction类的logTransaction函数改为non-virture,然后要求BuyTransaction传递必要的信息给Transaction构造函数,然后那个函数便可以安全地调用non-virture的logTransaction函数。

class Transaction{
public:
Transaction(const std::string& info);
void logTransaction(const std::string& info) const;
};

class BuyTransaction: public Transaction{
public:
BuyTransaction(parameters): Transaction(createLogString(parameters)){
......
}
private:
static std::string createLogString(parameters);
};        换句话说,你无法使virture函数从base class向下调用,在构造期间,你可以藉由“令derived class将必要的构造信息向上传递至base class的构造函数”替换之而加以弥补。比起使用成员初始化列表赋予base class所需数据,使用一个辅助函数来创建一个值传递给base class往往比较方便。而令此函数为static,也不可能指向“初期未成熟的BuyTransaction对象内尚未初始化的成员变量”。

               
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息