2013-09-26 54 views
1

比方说,我有以下代码:如何清理资源

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

基地和BaseMember是API的一部分,并可以通过API的用户被继承,就像它通过示例代码中的Derived和DerivedMember完成的一样。

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

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

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

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

有没有其他方法?

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

+0

工作解决方案值得一提的明确规定,你不能调用基类的析构函数派生类的功能派生类将已经被破坏。 – Bathsheba

+0

您的实施违反了“资源获取初始化”习惯用法。我宁愿问一下在基本或派生构造函数中如何分配内存(+1)。 – cpp

+1

智能指针有什么问题? – doctorlove

回答

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

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

析构函数调用顺序是孩子,然后父

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

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:为什么析构函数是虚拟的?

+0

“永远不会永远不会调用构造函数/析构函数中的虚拟方法,因为它会产生奇怪的结果” 这是非常清楚的。否则我首先不会问这个问题,因为整个问题是“我知道,我不能在构造函数和析构函数中调用这些虚函数,我可以很容易地避免构造函数中的调用,但是如何避免析构函数的调用,并保证所有资源都被释放?“ – Kaiserludi

+0

“(破坏者是虚拟的!?)” 是的。你的问题的意图是什么? – Kaiserludi

+0

这是一个普遍的问题,我会写“?”相反“!?” ;) – 56ka

1

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

(当然,这是从工厂模式很远......也许这是另一个名字知道的?)

+0

并在哪里清理工厂本身? – Kaiserludi

+0

我有更多的想法:我不需要为每个类实例创建一个工厂实例,而只需要为整个类创建一个工厂实例,所以让Factory可以返回静态函数成员的地址,而且我不必动态分配工厂,因此我不必担心如何清理它。 我刚刚试图实现你的想法,它似乎工作得很好(请参阅我自己的实现答案)。 – Kaiserludi

+0

是的,这几乎是我的想法,再加上静态实例的巧妙技巧。我的想法是,desctructor'〜Base'只会做'删除mpMemberFactory'(这里没有虚拟方法的问题)。 – volferine

0

首先,用C开发时必须使用RAII成语++。你必须必须解析器中释放你所有的资源,当然如果你不想与内存泄漏作斗争。

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

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

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

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

+0

问题是:如果在基类析构函数中检测到cleanupMember()尚未被调用,那么已经晚于做派生类的清理工作。 “我也建议你在这里使用智能指针。” 他们在这里会有什么不同? 作为基类成员的智能指针会在基类析构函数被调用时释放其有效负载,但是由于已经调用了派生类构造函数,因此我已经迟到了,无法调用虚函数的派生实现。 – Kaiserludi

+0

同样,你不能从'〜Base'调用任何虚函数。它必须在RAII规则中工作,所以当你进入'〜Base'时,'〜Derived'已经被调用,并且资源被释放。 智能指针只是给你一个晚上睡觉的机会,而不是调试内存泄漏。 –

0

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

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成员的责任划分为派生类是复杂的,只是要求麻烦。

其次,你初始化mpMember的方式意味着成员有不同的接口,取决于谁初始化它。你已经遇到的部分问题是,你初始化成员的信息已被销毁到~Base()的类型。同样,试图为同一个变量设置不同的接口只是要求麻烦。

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

这只隐藏在成员的接口的破坏部分的区别:

我们至少可以使用类似shared_ptr它允许了指定缺失者解决的第一个问题。如果您有更多元素的数组,则该成员的不同用户仍然必须能够确定mpMember[2]是否合法。

+0

+1,在此实现中,内存在构造函数中分配并在析构函数中释放。 – cpp

+0

如果Derived的作者错误地让它的构造函数调用Base的默认构造方法,那么此解决方案将无法工作, Base私有的默认构造函数,因为这也会阻止它在其他地方的使用,所以这很大程度上依赖于与派生作者沟通,他必须做一些特殊的事情。如果我可以在我的代码库中使用std :: shared_ptr,那么这仍然是迄今为止最好的选择。 – Kaiserludi

+0

@Kaiserludi“与派生作者沟通,他必须做一些特别的事情”这对于永远正确地使用继承是非常必要的。这是组合优于继承的原因之一。但是,我没有看到问题在这里与一些派生类调用默认的基础构造函数。 '派生类:virtual public Base {Derived():Base(){}};'仍然正确清理成员。 – bames53