如何清理虚拟成员函数分配的资源

时间:2013-09-26 15:56:42

标签: c++ polymorphism c++03

假设我有以下代码:

class BaseMember
{
};

class DerivedMember : public BaseMember
{
};

class Base
{
private:
    BaseMember* mpMember;
protected:
    virtual BaseMember* initializeMember(void)
    {
        return new BaseMember[1];
    }

    virtual void cleanupMember(BaseMember* pMember)
    {
        delete[] pMember;
    }
public:
    Base(void)
        : mpMember(NULL)
    {
    }

    virtual ~Base(void)
    {
        cleanupMember(mpMember);
    }

    BaseMember* getMember(void)
    {
        if(!mpMember)
            mpMember = initializeMember();
        return mpMember;
    }
};

class Derived : public Base
{
protected:
    virtual BaseMember* initializeMember(void)
    {
        return new DerivedMember;
    }

    virtual void cleanupMember(BaseMember* pMember)
    {
        delete pMember;
    }
};

Base和BaseMember是API的一部分,可以由该API的用户进行子类化,就像通过示例代码中的Derived和DerivedMember一样。

Base通过调用它的虚拟工厂函数initializeMember()来初始化mpBaseMember,以便派生类可以覆盖工厂函数以返回DerivedMember实例而不是BaseMember实例。

但是,从基类构造函数中调用虚函数时,将调用基本实现而不是派生类重写。 因此,我等待mpMember的初始化,直到它第一次被访问(当然这意味着,基类和任何派生类,可能可以进一步自己派生,不允许从内部访问该成员)构造函数)。

现在的问题是:从基本基础析构函数中调用虚拟成员函数将导致调用该函数的基类实现,而不是派生类重写。 这意味着我不能简单地从基类析构函数中调用cleanupMember(),因为这会调用它的基类实现,它可能无法正确清理initizeMember()的派生实现已初始化的东西。 例如,基类和派生类可能使用不兼容的分配器,这些分配器在混合时可能会导致未定义的行为(如示例代码中 - 派生类通过new分配成员,但基类使用delete []取消分配)

所以我的问题是,我该如何解决这个问题? 我想出的是: a)在Derived实例被破坏之前,API的用户必须显式调用一些清理函数。这可能会被遗忘。 b)(大多数)派生类的析构函数必须调用一个清理函数来清理由基类触发初始化的东西。由于所有权责任混淆,这种感觉很丑陋而且没有很好地设计:基类触发分配,但派生类必须触发释放,这是非常反直觉的,派生类的作者无法知道,除非他读取API文档足以找到该信息。 我真的希望以更加防错的方式做到这一点,而不是依靠用户内存或他的可靠性来彻底阅读文档。

有没有其他方法?

注意:由于派生类在基类的编译时可能不存在,因此静态多态不是一个选项。

6 个答案:

答案 0 :(得分:1)

修改包含清理方法的工厂模式怎么样?意思是,添加一个属性,如memberFactory,一个提供创建,清理的类的实例,以及对成员的访问。虚拟初始化方法将提供并初始化正确的工厂,析构函数~Base将调用工厂的清理方法并销毁它。

(嗯,这距离工厂模式还很远......也许它以另一个名字而闻名?)

答案 1 :(得分:1)

如果你真的想做这类事,你可以这样做:

class Base {
    BaseMember* mpMember;

  protected:
    Base(BaseMember *m) : mpMember(m) {}

    virtual void doCleanupMember(BaseMember *m) { delete [] m; }

    void cleanupMember() {
      // This gets called by every destructor and we only want
      // the first call to do anything. Hopefully this all gets inlined.
      if (mpMember) {
        doCleanupMember(pmMember);
        mpMember = nullptr;
      }
    }

  public:
    Base() : mpMember(new BaseMember[1]) { }
    virtual ~Base(void) { cleanupMember(); }
};

class Derived : public Base {
  virtual void doCleanupMember(BaseMember *m) override { delete m; }

  public:
    Derived() : Base(new DerivedMember) {}
    ~Derived() { cleanupMember(); }
};

但是有理由这是个坏主意。

首先,该成员应由Base独家管理。试图将Base的成员责任划分为派生类是很复杂的,只是要求麻烦。

其次,初始化mpMember的方式意味着该成员具有不同的界面,具体取决于谁初始化它。您已遇到的部分问题是,有关初始化成员的人的信息已被您~Base()的类型破坏。再次,尝试为同一个变量设置不同的接口只是在寻找麻烦。

我们至少可以通过使用shared_ptr之类的内容来解决第一个问题,这样可以指定一个删除器:

class Base {
    std::shared_ptr<BaseMember> mpMember;
  public:
    Base(std::shared_ptr<BaseMember> m) : mpMember(m) { }
    Base() : mpMember(std::make_shared<BaseMember>()) { }
    virtual ~Base() {}
};

class Derived : virtual public Base {     
  public:
    Derived()
      : Base(std::shared_ptr<BaseMember>(new DerivedMember[1],
                                         [](BaseMember *m){delete [] m;} ) {}
};

这只隐藏了成员界面的破坏部分的差异。如果您有一系列更多元素,该成员的不同用户仍然必须能够确定mpMember[2]是否合法。

答案 2 :(得分:0)

永远不要永远不要在构造函数/析构函数中调用虚方法,因为它会产生奇怪的结果(编译器会产生你看不见的黑暗和奇怪的东西)。

析构函数调用顺序是child,然后是parent

你可以这样做(但有一种更好的方法):

private:
    // private destructor for prevent of manual "delete"
    ~Base() {}

public:
    // call this instead use a manual "delete"
    virtual void deleteMe()
    {
        cleanupMember(mpMember);
        delete this; // commit suicide
    }

关于自杀的更多信息: https://stackoverflow.com/a/3150965/1529139http://www.parashift.com/c++-faq-lite/delete-this.html

PS:为什么析构函数是虚拟的?

答案 3 :(得分:0)

首先,在使用C ++进行开发时必须使用RAII习惯用法。你必须释放析构函数中的所有资源,当然如果你不想与内存泄漏作斗争。

你可以创建一些cleanupMember()函数,但是你应该在析构函数中检查你的资源并在它们没有被删除时释放它们(因为cleanupMember永远不会被调用,例如因为异常) 。因此,在派生类中添加析构函数:

virtual ~Derived()
{
    Derived::cleanupMember(mpMember);
}

并管理类本身中的成员指针。

我还建议你在这里使用智能指针。

答案 4 :(得分:0)

mpMember受到保护,让它在派生类构造函数中初始化,并在派生的析构函数中释放。

答案 5 :(得分:0)

受到https://stackoverflow.com/a/19033431/404734的想法的启发,我提出了一个有效的解决方案: - )

class BaseMember
{
};

class DerivedMember : public BaseMember
{
};

class BaseMemberFactory
{
public:
    virtual ~BaseMemberFactory(void);

    virtual BaseMember* createMember(void)
    {
        return new BaseMember[1];
    }

    virtual void destroyMember(BaseMember* pMember)
    {
        delete[] pMember;
    }
};

class DerivedMemberFactory : public BaseMemberFactory
{
    virtual BaseMember* createMember(void)
    {
        return new DerivedMember;
    }

    virtual void destroyMember(BaseMember* pMember)
    {
        delete pMember;
    }
};

class Base
{
private:
    BaseMemberFactory* mpMemberFactory;
    BaseMember* mpMember;
protected:
    virtual BaseMemberFactory* getMemberFactory(void)
    {
        static BaseMemberFactory fac;
        return &fac;
    }
public:
    Base(void)
        : mpMember(NULL)
    {
    }

    virtual ~Base(void)
    {
        mpMemberFactory->destroyMember(mpMember);
    }

    BaseMember* getMember(void)
    {
        if(!mpMember)
        {
            mpMemberFactory = getMemberFactory();
            mpMember = mpMemberFactory->createMember();
        }
        return mpMember;
    }
};

class Derived : public Base
{
protected:
    virtual BaseMemberFactory* getMemberFactory(void)
    {
        static DerivedMemberFactory fac;
        return &fac;
    }
};