封装对象的生命周期管理

时间:2009-01-12 20:02:30

标签: c++ memory-management encapsulation

封装对象和管理其生命周期的最佳方法是什么?示例:我有一个 A 类,其中包含 B 类型的对象,并且对此负责

解决方案1 ​​,克隆 b 对象以确保只有 A 能够清理它。

class A
{
    B *b;
public:
    A(B &b)
    {
        this->b = b.clone();
    }

    ~A()
    {
        delete b; // safe
    }
};

解决方案2 ,直接使用传递的对象,我们冒险潜在的双重免费。

class A
{
    B *b;
public:
    A(B *b)
    {
        this->b = b;
    }

    ~A()
    {
        delete b; // unsafe
    }
};

在我的实际案例中,解决方案#2最适合。但是我想知道这是否被认为是错误的代码,因为有人可能不知道 A 的行为,即使它已被记录。我可以想到这些场景:

B *myB = new B();
A *myA = new A(myB);
delete myB; // myA contains a wild pointer now

或者,

B *myB = new B();
A *firstA = new A(myB);
A *secondA = new A(myB); // bug! double assignment
delete firstA; // deletes myB, secondA contains a wild pointer now
delete secondA; // deletes myB again, double free

如果我正确记录A的行为,我可以忽略这些问题吗?是否足以宣布责任并让其他人阅读文档?如何在您的代码库中管理它?

6 个答案:

答案 0 :(得分:5)

除非我真的需要,否则我自己永远不会删除任何内容。这会导致错误。

聪明的指针是你的朋友。当一个对象拥有另一个对象时,std::auto_ptr<>是您的朋友,并且当超出范围时负责将其删除。 boost::shared_ptr<>(或者,现在,std::tr1::shared_ptr<>)是您的朋友,当可能有多个对象附加到另一个对象时,您希望在没有更多引用的情况下删除该对象。

因此,要么将解决方案1与auto_ptr一起使用,要么将解决方案2与shared_ptr一起使用。

答案 1 :(得分:4)

您应该定义您的对象,以便尽可能由接口定义所有权语义。正如David Thornley指出的那样,std :: auto_ptr是指示所有权转移的智能指针。像这样定义你的类:

class A
{    
    std::auto_ptr<B> b;
public:    
    A(std::auto_ptr<B> b)    
    {
        this->b = b;
    }
    // Don't need to define this for this scenario
    //~A()
    //{ 
    //   delete b; // safe
    //}
};

由于std :: auto_ptr的契约是赋值=所有权转移,你的构造函数现在隐式地声明A对象拥有指向它传递的B的指针。实际上,如果客户端尝试使用std :: auto_ptr&lt; B&gt;执行某些操作。他们在构造之后用来构造A,操作将失败,因为他们持有的指针将无效。

答案 2 :(得分:2)

如果您正在编写其他人稍后将使用的代码,则必须解决这些问题。在这种情况下,我会去简单的引用计数(可能有智能指针)。请考虑以下示例:

当为封装类的实例分配了一个对象B时,它会调用一个方法来增加对象的B引用计数器。当封装类被销毁时,它不会删除B,而是调用方法确实减少引用计数。当计数器达到零时,对象B被破坏(或者为此而破坏自身)。这样,封装类的多个实例可以与对象B的单个实例一起使用。

有关此主题的更多信息:Reference Counting

答案 3 :(得分:2)

如果您的对象全权负责传递的对象,则删除它应该是安全的。如果不完全断言你自己负责是假的。那是哪个呢?如果您的接口被记录为您将删除入站对象,则调用者有责任确保您收到必须由您删除的对象。

答案 4 :(得分:1)

如果您正在克隆A,并且A1和A2都保留对B的引用,则B的生命周期不完全由A控制。它在各种A之间共享。克隆B确保了一对一之间的关系As和Bs,这将很容易确保终身的一致性。

如果克隆B不是一个选项,那么你需要抛弃A负责B的生命周期的概念。另一个对象需要管理各种B,否则你需要实现像引用计数这样的方法。

作为参考,当我想到“克隆”一词时,它意味着一个深层拷贝,它也会克隆B。我希望在克隆之后两个As完全脱离。

答案 5 :(得分:0)

我不会不必要地克隆东西或“只是为了安全”。

相反,我知道删除某些内容的责任是:通过文档或智能指针...例如,如果我有一个create函数实例化某些内容并返回一个指向它的指针并且没有删除它,以便不清楚那个东西应该被删除的地方和那个,然后代替create返回一个裸指针我可能会定义create的返回类型作为返回指针包含在某种智能指针中。