在设计模式工厂(factory)模式中,我们通常会返回一个指针,指向一个子类。即返回一个base class
的指针,指向一个新生成的derived class
的对象。
class Base
{
public:
Base(){}
~Base(){}
...
};
class Derived1 : public Base{...}
class Derived2 : public Base{...}
class Derived3 : public Base{...}
像下面的函数,返回一个指针,指向一个新生成的Derived 类的对象。
Base* getBase();
被getBase返回的对象都在堆上,也就是资源和内存有程序员自己控制。而为了避免资源和内存的泄漏,将上面函数返回的对象正确的释放是我们必须要保证的。
Base* ptr = getBase();
...
delete ptr;
上面的例子我们正常的进行了析构吗?我们感觉好像是做对了所有的事情,但我们还是没有办法知道程序怎样运行。
问题就在于,函数getBase()
返回的是一个指向derived class
的base class
指针,所以,我们看到的derived class 的对象是由base class指针删除。好巧不巧的是,base class有一个 non-virtual的析构函数。
所以实际执行时,derived class 的部分通常是没有被删除的,也就是说derived class中的成员变量等的内存是没有被销毁的,这也就造成了内存和资源的泄漏。
而解决上面问题的方法也是很简单的,就是给base class声明一个virtual 析构函数。此后在删除对象时,就会对所有的对象都进行删除。包括其派生的部分。
class Base
{
public:
Base(){}
virtual ~Base(){}
...
};
Base* ptr = getBase();
...
delete ptr;
而像上面这种例子,一般情况下,基类 Base 除了virtual 的析构函数之外,还会有一些其他的virtual函数,这个函数在不同的派生类中有不同的实现。
也就是说,当一个类有倾向于被当作基类的时候,一般类中都会至少有一个virtual函数,我们应该将其析构函数声明为virtual类型。这样做的目的是为了避免将delete动作,交由客户来使用,从而出现一些资源泄漏等不必要的问题。
但是,我们给任何一个类都定义virtual析构函数是正确的吗?
其实并不是这样的,如果一个class中不含有virtual类型的函数,通常表示他并不会意图被当作是base class,而如果一个类如果不被当作base class,将其析构函数声明为virtual类型,也会造成内存和资源的浪费。
class Point
{
public:
Point(int x, int y);
~Point();
private:
int m_x{ 0 };
int m_y{ 0 };
}
考虑上面的类。一个点有两个坐标,如果每个int类型占用32bits,那么Point的对象就只占64bits。
但是如果将他的析构函数声明为virtual类型呢?
我们都知道,如果一个类中含有虚函数,类中会自己维护一个虚函数表,用来存储对象被调用虚函数时实际被调用的函数的指针。
也就是说,如果我们将其析构声明为virtual类型,在对象内部增加虚函数表,至少会增加对象大小的50% - 100%。
所以,将不意图做为base class的类的析构函数声明为virtual是错误的。
只有当class内含有至少一个virtual函数时,才为他声明virtual析构函数。
class MyString : public std::string
{
...
}
MyString *mps = new MyString("this is a test example");
std::string *ps;
...
ps = mps;
delete ps;
上面的例子我们可能会遇到,这种情况下也会出现不明确行为,因为std::string类是没有virtual类型的虚构函数的。
- 带多态性质的base class 应该声明一个virtual析构函数,如果class有至少一个virtual函数,就应该声明virtual析构函数。
- class的设计目的如果不是做为base class使用,或者不是为了具备多态性,就不该声明virtual析构函数。