如何处理指针成员的不同所有权策略?

时间:2010-07-28 19:07:01

标签: c++ memory-management pointers ownership

考虑以下类结构:

class Filter
{
    virtual void filter() = 0;
    virtual ~Filter() { }
};

class FilterChain : public Filter
{
    FilterChain(collection<Filter*> filters)
    {
         // copies "filters" to some internal list
         // (the pointers are copied, not the filters themselves)
    }

    ~FilterChain()
    {
         // What do I do here?
    }

    void filter()
    {
         // execute filters in sequence
    }
};

我在库中暴露了这个类,所以我无法控制它的使用方式。

我目前有一些关于Filter对象FilterChain的所有权的设计问题正在抓住指针。更具体地说,以下是FilterChain的两种可能的使用方案:

  • 场景A:我的库中的一些函数正在构建一个(可能是复杂的)过滤器链,根据需要分配内存,并返回一个新分配的FilterChain对象。例如,这些函数之一构造了一个文件的过滤器链,它可以描述任意复杂的过滤器(包括过滤器链的过滤器链等)。一旦完成作业,该功能的用户负责对象销毁。
  • 场景B:用户可以访问一堆Filter个对象,并希望以特定方式将它们组合在过滤器链中。用户构造FilterChain个对象供自己使用,然后在完成后将其销毁。当引用它们的Filter被销毁时,FilterChain个对象不得被销毁。

现在,管理FilterChain对象所有权的两种最简单的方法是:

  • FilterChain拥有Filter个对象。这意味着FilterChain引用的对象在FilterChain的析构函数中被销毁。这与方案B不兼容。
  • FilterChain 拥有Filter个对象。这意味着FilterChain的析构函数什么都不做。现在方案A存在问题,因为用户必须知道所涉及的所有Filter个对象的内部结构,以便在不丢失所有对象的情况下将其全部销毁,因为父FilterChain不做它本身。这只是糟糕的设计,并要求内存泄漏。

因此,我需要更复杂的东西。我的第一个猜测是设计一个带有可设置布尔标志的智能指针,指示智能指针是否拥有该对象。然后,Filter不会收集指向FilterChain个对象的指针集合,而是会收集到Filter个对象的智能指针集合。当调用FilterChain的析构函数时,它会破坏智能指针。然后智能指针本身的析构函数会破坏被指向的对象(Filter对象)当且仅当指示所有权的布尔标志被设置时。

我觉得这个问题在C ++中很常见,但我的网络搜索流行的解决方案或聪明的设计模式并不是很成功。确实,auto_ptr在这里并没有真正的帮助,shared_ptr似乎有点矫枉过正。那么,我的解决方案是否是一个好主意?

4 个答案:

答案 0 :(得分:2)

过滤器是否过大,以至于在创建FilterChain时,您无法简单地为每个过滤器制作一份深层副本?如果你能够做到这一点,那么你所有的问题都会消失:FilterChain总是会自行清理。

如果由于内存问题而不是选项,那么使用shared_ptr似乎最有意义。调用者必须负责为其关注的每个对象保留shared_ptr,然后FilterChain将知道在delete d时是否删除特定过滤器。

编辑:正如Neil所说,Filter需要一个虚拟析构函数。

答案 1 :(得分:2)

这里的智能指针并不过分:显然你有一个设计问题,不管怎样都需要仔细考虑对象的生命周期和所有权。如果您希望能够在运行时重新修补过滤器图形中的过滤器,或者能够创建复合FilterChain对象,则尤其如此。

使用shared_ptr将一举消除大部分问题,使您的设计更加简单。我认为唯一可能的问题是你的过滤器是否恰好包含循环。我可以看到,如果您有某种反馈循环,可能会发生这种情况。在那个例子中,我建议让一个类拥有所有Filter个对象,然后FilterChain将存储指向Filter个对象的弱指针。

我打赌过滤阶段的执行时间远远超过解除引用智能指针的额外开销。 shared_ptr的设计非常轻巧。

答案 2 :(得分:0)

FilterChain应该有一个单独的DeleteAll()方法,该方法对集合进行迭代,并delete为过滤器。在场景A中调用 ,在场景B中调用 。这确实需要FilterChain用户的一些情报,但不能再记住{{ 1}}并反对他们delete'd。 (他们应该能够处理,或者他们应该得到记忆泄漏)

答案 3 :(得分:0)

我会使用FilterChain不拥有Filter对象。然后,在您的库中,当您需要从文件加载FilterChain时,您将拥有另一个Loader对象,该对象负责Filter对象的生命周期。因此,FilterChain将为库和用户创建的链加载的链一致地工作。