Effective C++——条款07:为多态基类声明virtual析构函数

Effective C++——条款07:为多态基类声明virtual析构函数,第1张

Declare destructors virtual polymorphic base classes

在这里笔者引用之前写过的一篇文章中的片段

C++——多态___JAN__的博客-CSDN博客

读者若读者已经了解多态,那么仅仅看一看其中的多态的更多用法那里即可——关于本篇文章的。


        如果读者已经看过了上面文章中的内容,那么对多态基类中需要一个virtual虚构函数应该使不会惊讶。


        构造函数的运作方式是,最深层派生的那个类其析构函数最先被调用,然后是其每一个基类的析构函数紧接着被调用,所以,对象的析构顺序是 继承类 --> 派生类,唯一有一个例外,就是在多态的情况下没有virtual析构函数并且以delete 基类指针形式释放内存,这就会导致直接调用基类中的构造函数,没有进行派生类的析构,造成内存溢出。


        有的人可能会这样想,为每一个类都声明一个virtual析构函数不久好了吗——不用考虑上述的这类问题了。


事实上,这样做确实可以避免这类问题,但是,如果熟悉C++内存模型的读者应该会看出来一些问题。


或许说,为什么C++不像java那样默认为虚函数呢?

C++的设计理念是,用户没有用到的东西,不应该给用户添加任何负担,因为使用多态技术会增加内存的开销,所以C++默认不使用虚函数。


 

        就像书中所说的那样,如果class不含virtual函数,通常表示它并不被用作一个 base class。


当class不企图被当作base class,令其析构韩式为virtual往往是个馊主意。


 考虑一个用来表示二维空间点坐标的class:

class Pointe
{
public:
    Pointe(int xCoord, int yCoord);
    ~Pointe();
private:
    int x;
    int y;
};

        如果一个int占32bits,那么point对象可塞入一个64-bit缓存器中。


更有甚者,这样一个point对象可被当作一个64-bits量掺入给其他语言如C或者FORTRAN撰写的函数,然而当pointe的析构函数是virtual,形势引起了变化。


关于C++的编译器是如何实现多态的,上面的链接也有说明。


        因此无端的将所有class的析构函数就声明为virtual,就像从未声明他们为virtual一样,都是错误的。


许多人的心得是:当只有class内含至少一个virtual函数,才将它声明为virtual函数。


        即使class完全不带virtual函数,被"non-virtual"析构函数问题给要上还是有可能的。


举个例子,std::string不含任何virtual函数,单有时候程序员会错误地把它当作base class

class SpecialString : public std::string
{
    ...
};

         这样起初一看似乎无害,但如果使用std::string * 来释放SpecialString的内存会得到一个不明确的行为。


        因为string问有virtual析构函数,所以资源会泄漏,也就是说——因为SpecialString的对象的析构函数没有被调用。


STL的容器vector list set st1::unordered_map等等。


C++11提供了final的关键字,用于处理多态和继承,如果不想类被继承,那么在类定义的时候用final即可。


class People final
{
private:
    string & name;
    const int age;
public:
    People() = default;
};

        如果我们希望获得一个纯虚基类,而手头又没有任何的纯虚函数,我们可以将其析构函数声明为纯虚函数并予以空实现或者对应的实现。


就像下面这样。


class People
{
private:
    string * name;
    int age;
public:
    People(const string & _name, const int _age) : name(new string(_name)), age(_age) { }
    virtual ~People() = 0;
};

People::~People() {
    delete name;
    cout << "use People destructor " << endl;
}

class Stu : public People
{
private:
    int grade;
public:
    Stu(const string & _name, const int _age, const int _grade) : People(_name,_age), grade(_grade) { }
    ~Stu() override
    {
        cout << "use Stu destructor " << endl;
    }
};

再次具体说明以下析构函数的规则

关于非多态,只会从引用或者指针类型来判断调用继承链中的哪个析构函数,接着向上一级一级调用父类的析构函数。


如果是多态,根据引用或者指针所指的对象来判断调用继承链中的哪个析构函数,接着向上一级一级调用。


但是无论怎样调用,析构函数需要一个明确的定义——确保其能被执行,所以,应当给定定义。


        给base classes一个virtual析构函数,这个规则只适用于带多态性质的基类身上。


这种基类的实际目的是为了用来通过base classes接口处理继承类对象。


        并非所有的类的设计目的都是用于继承用途,例如std::string。


有些类的设计可以用作基类,但不用于多态。


因此他们不需要virtual。


        上述说了这么多,但其实真正的原则就是

        请记住

        polymorphic(带有多态性质的)base classes应该声明一个virtual函数。


如果class带有任何virtual函数,特就应该拥有一个virtual析构函数。


        Classes的设计如果不是作为base classes使用,或者不是为了具备多态性(polymorphic),就不应该声明virtual析构函数。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/565166.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-06
下一篇 2022-04-06

发表评论

登录后才能评论

评论列表(0条)

保存