zoukankan      html  css  js  c++  java
  • Effective C++学习笔记:确定基类有虚析构函数

     

    设想在一个军事应用程序里,有一个表示敌人目标的类:

    class enemytarget

    {
    public:
    enemytarget() { ++numtargets; }
    enemytarget(const enemytarget&) { ++numtargets; }
    ~enemytarget() { --numtargets; }

    static size_t numberoftargets()
    { return numtargets; }

    virtual bool destroy(); // 摧毁enemytarget对象后
    // 返回成功

    private:
    static size_t numtargets; // 对象计数器
    };

    //类的静态成员要在类外定义;
    // 缺省初始化为0

    size_t enemytarget::numtargets;

    敌人的坦克是一种特殊的敌人目标,所以会很自然地想到将它抽象为一个以公有继承方式从enemytarget派生出来的类。因为不但要关心敌人目标的总数,也要关心敌人坦克的总数,所以和基类一样,在派生类里也采用了上面提到的同样的技巧:

    class enemytank: public enemytarget {
    public:
    enemytank() { ++numtanks; }

    enemytank(const enemytank& rhs)
    : enemytarget(rhs)
    { ++numtanks; }

    ~enemytank() { --numtanks; }

    static size_t numberoftanks()
    { return numtanks; }

    virtual bool destroy();

    private:
    static size_t numtanks; // 坦克对象计数器
    };

    最后,假设程序的其他某处用new动态创建了一个enemytank对象,然后用delete删除掉:

    enemytarget *targetptr = new enemytank;

    ...

    delete targetptr

    这样会发生严重问题,因为c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。实际运行时经常发生的是,派生类的析构函数永远不会被调用。在本例中,这意味着当targetptr 删除时,enemytank的数量值不会改变,那么,敌人坦克的数量就是错的。

    如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。因为它会为类增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移植性。

    所以基本的一条是:无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。

    抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。

    这里是一个例子:

    class awov { // awov = "abstract w/o
    // virtuals"
    public:
    virtual ~awov() = 0; // 声明一个纯虚析构函数
    };

    这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:

    awov::~awov() {} // 纯虚析构函数的定义

    这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。

    注意:如果声明虚析构函数为inline,将会避免调用它们时产生的开销,但编译器还是必然会在什么地方产生一个此函数的拷贝。

  • 相关阅读:
    CentOS7搭建SFTP服务
    MySQL主从异常恢复
    MySQL主从复制配置
    Docker安装MySQL8.0
    CentOS7安装JDK1.8
    RabbitMQ死信队列
    RabbitMQ重试机制
    RabbitMQ消息可靠性传输
    TCP/IP的Socket编程
    c#网络编程使用tcpListener和tcpClient
  • 原文地址:https://www.cnblogs.com/xiayong123/p/3717208.html
Copyright © 2011-2022 走看看