关于c ++:适合在派生类析构函数中调用虚函数吗?


Is appropriate to call virtual function in derived class destructor?

我有一个继承,基类的析构函数应用模板方法模式。析构函数在调用虚clean函数之前必须做一些工作,在调用之后还要做一些其他工作。

我们知道在构建或破坏过程中永远不要调用虚拟函数。所以下面的代码绝对不可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class base
{
public:
    virtual ~base()
    {
        // ... do something before do_clear()
        do_clear();
        // ... do something after do_clear()
    }

private:
    virtual void do_clear() = 0;
};

class d1
    : public base
{
public:
    d1() : m_x(new int) {}
    ~d1() {}

private:
    virtual void do_clear()
    {
        delete m_x;
    }
    int *m_x;
};

但如果我将进程的销毁移到派生类的析构函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class base
{
public:
    virtual ~base()
    {
    }

protected:
    void clear()
    {
        // ... do something before do_clear()
        do_clear();
        // ... do something after do_clear()
    }

private:
    virtual void do_clear() = 0;
};

class d1
    : public base
{
public:
    d1() : m_x(new int) {}
    ~d1()
    {
        clear();
    }

private:
    virtual void do_clear()
    {
        delete m_x;
    }
    int *m_x;
};

如果客户写下:

1
2
base *x = new d1;
delete x;

它先调用~d1(),然后调用base::clear(),最后正确调用虚函数d1::do_clear()

base::clear()可以公开,客户可以在销毁前致电base::clear()进行安全处理。前提是客户必须知道并且不要忘记调用,我认为这不方便并且破坏了封装。

我的问题是:

  • 设计是否危险/风险?
  • 现有的其他更好的设计吗?

  • 您当前的设计有两个问题。第一,它违反了五法则/零法则。这意味着这些类的正常使用几乎肯定会导致内存泄漏或双重删除。

    第二个问题是,您使用继承来建模一些可能更好地用组合建模的东西。base希望d1为其析构函数提供一些额外的功能,并在运行时指定此功能的确切形式。因此,可替换接口的使用是base内部的,因此不应在外部可见。

    以下是我如何编写此代码(使用wheels::value_ptr):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    struct D_interface {
        //Providing virtual functions for run-time modifiable behaviour
        virtual D_interface *clone() const = 0;
        virtual ~D_interface(){}
    };

    struct D_Delete {
        //Here is the code to call through to the virtual `D` object behaviour:
        void operator()(D_interface *p) {
            // ... do something before delete p;
            delete p;
            // ... do something after delete p;
        }
        //Put (pointers to) relevant data here,
        //initialise them when constructing the `value_ptr`, or
        //at a later stage with get_deleter
    };

    struct d1 : D_interface {
        wheels::value_ptr<int> mx;
        virtual D_interface *clone() const {
            return new d1(*this);
        }
    };

    //Nothing derives from `base`, because the polymorphism that is needed is internal
    //to the implementation of `base`
    //To add new functionality, add a new `D_interface` implementation.
    class base
    {
        wheels::value_ptr<D_interface, wheels::DefaultCloner<D_interface>, D_Delete> D_impl;
        public:
        base(D_interface *D_impl)
            : D_impl(D_impl)
        {
        }
    };


    对于我来说,我认为这个模式是确定的,因为您需要在派生类中实现虚函数。这就是虚拟课堂的哲学。