关于使用析构函数的几点注意事项
2014-07-31 17:25
459 查看
析构函数为你提供了一种机制,可以让你在系统释放对象前做一些善后工作,如清理内存,释放空间等。但是在以下两个方面使用虚构函数时要尤为注意。
1. 多态性
具有多态性质的base classes的析构函数应该为virture,或者说任何带有virture函数的class都应该有virture析构函数。考虑下面的代码:
如果以上代码出现在你的项目中,那么就会造成灾难性的后果。在上述代码中,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函数,否则会给你带来意料不到的结果。下面的例子可以说明问题:
毫无疑问,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对象内尚未初始化的成员变量”。
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对象内尚未初始化的成员变量”。
相关文章推荐
- 关于Aspose.NET使用的几点注意事项
- 关于使用READ TABLE语句的几点注意事项
- 关于使用READ TABLE语句的几点注意事项...(原文来源于网络)
- Android中关于线程使用的几点注意事项
- 关于使用READ TABLE语句的几点注意事项
- Android中关于线程使用的几点注意事项
- Android中关于线程使用的几点注意事项
- ABAP 筑基宝典(5) -- 关于使用READ TABLE语句的几点注意事项
- 关于笔记本使用的几点注意事项
- 关于postman使用的几点注意事项
- 关于Aspose.NET使用的几点注意事项
- 关于使用READ TABLE语句的几点注意事项...(原文来源于网络)
- 关于使用READ TABLE语句的几点注意事项
- 关于使用ZXing开发几点注意事项
- 关于笔记本使用的几点注意事项
- Android中关于线程使用的几点注意事项
- 关于在oracle中是使用索引的几点注意事项
- 关于网格比较工具metro使用的几点注意事项
- 关于Aspose.NET使用的几点注意事项
- 关于使用READ TABLE语句的几点注意事项