【问题标题】:Is appropriate to call virtual function in derived class destructor?在派生类析构函数中调用虚函数合适吗?
【发布时间】:2014-04-23 05:57:02
【问题描述】:

我有一个继承,基类的析构函数应用Template Method Pattern。析构函数在调用virtual clean函数之前要做一些工作,调用之后还要做一些工作。

我们知道Never Call Virtual Functions during Construction or Destruction。所以下面的代码肯定是不可用的。

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;
};

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

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;
};

如果客户这样写:

base *x = new d1;
delete x;

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

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

我的问题是:

  1. 设计是否有危险/风险?
  2. 是否存在其他更好的设计?

【问题讨论】:

  • 如果在销毁对象之前需要满足一些先决条件,我认为它应该是析构函数本身的一部分,这是一个好兆头。为什么不能在派生的析构函数中简单地delete m_x
  • 在析构函数、构造函数中搜索调用虚函数,应该能找到。
  • 您在 (1) 的列表中遗漏了“/insidious”;一个形容词,用于描述您在从 Scott Meyers 链接的文章的第二个代码示例之后所建议的内容。您的派生类专门分配成员数据,然后依赖基类进行清理,这应该是一种邪恶的暗示。

标签: c++


【解决方案1】:

您当前的设计存在两个问题。首先是它违反了五规则/零规则。这意味着这些类的普通使用几乎肯定会导致内存泄漏或双重删除。

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

这是我将如何编写这段代码(使用wheels::value_ptr):

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)
    {
    }
};

【讨论】:

  • 是的,但要正确使用零规则。因此,如果您复制或分配您的 base 对象,您的代码将产生双重删除,并且如果您向 base 添加一些其构造函数可能抛出的成员,则会泄漏内存。
  • 谢谢,这个http://ideone.com/4kEgWo怎么样,我努力改进了。
【解决方案2】:

就我而言,我认为这种模式是肯定的,因为您需要在派生类中实现虚函数。这就是虚拟课程的理念。

【讨论】:

    猜你喜欢
    • 2020-09-29
    • 2015-04-16
    • 2015-02-24
    • 1970-01-01
    • 2011-09-27
    • 2021-03-20
    • 2019-04-19
    • 2015-07-18
    • 1970-01-01
    相关资源
    最近更新 更多