3

封装对象和管理其生命周期的最佳方法是什么?示例:我有一个类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 的行为,我可以忽略这些问题吗?声明责任并让其他人阅读文档就足够了吗?这是如何在您的代码库中管理的?

4

6 回答 6

5

除非我真的必须这样做,否则我自己从不删除任何东西。这会导致错误。

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

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

于 2009-01-12T20:20:05.520 回答
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 的指针的所有权。实际上,如果客户端尝试使用他们在构造后用来构造 A 的 std::auto_ptr<B> 做某事,则操作将失败,因为它们持有的指针将无效。

于 2009-01-12T20:28:13.353 回答
2

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

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

有关该主题的更多信息:引用计数

于 2009-01-12T20:11:42.997 回答
2

如果您的对象对传递的对象负全部责任,那么删除它应该是安全的。如果它不安全,那么您全权负责的断言是错误的。那么它是哪一个?如果您的接口记录了您将删除入站对象,那么调用者有责任确保您收到必须由您删除的对象。

于 2009-01-12T20:16:12.487 回答
1

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

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

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

于 2009-01-12T20:14:12.350 回答
0

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

相反,我知道删除某些东西是谁的责任:要么通过文档,要么通过智能指针......例如,如果我有一个create函数实例化某些东西并返回指向它的指针并且不删除它,所以不清楚该东西应该在哪里以及由谁来删除,那么我可以将' 返回类型定义为返回包含在某种智能指针中的指针,而不是create' 返回一个裸指针。create

于 2009-01-12T20:15:33.050 回答