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

发布于 2024-09-12 01:13:45 字数 1843 浏览 4 评论 0原文

考虑以下类结构:

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 对象供自己使用,然后在使用完毕后销毁它们。当引用 FilterChain 的 FilterChain 被销毁时,Filter 对象不得被销毁。

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

  • FilterChain 拥有 Filter 对象。这意味着 FilterChain 引用的对象将在 FilterChain 的析构函数中销毁。这与场景 B 不兼容。
  • FilterChain 不拥有 Filter 对象。这意味着 FilterChain 的析构函数不执行任何操作。现在场景 A 存在一个问题,因为用户必须知道所涉及的所有 Filter 对象的内部结构,以便将它们全部销毁而不会丢失一个,作为父 FilterChain< /code> 本身并不执行此操作。这只是糟糕的设计,并且会导致内存泄漏。

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

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

Consider the following class structure:

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

I'm exposing the class in a library, so I don't have control over how it will be used.

I'm currently having some design issues regarding ownership of the Filter objects FilterChain is holding pointers to. More specifically, here are two possible usage scenarios for FilterChain:

  • Scenario A: some of the functions in my library are constructing a (possibly complex) filter chain, allocating memory as necessary, and returning a newly-allocated FilterChain object. For example, one of these functions constructs a filter chain from a file, which can describe arbitrarily-complex filters (including filter chains of filter chains, etc.). The user of the function is responsible for object destruction once the job is done.
  • Scenario B: the user has access to a bunch of Filter objects, and wants to combine them in filter chains in a specific manner. The user constructs FilterChain objects for its own use, then destroy them when he's done with them. The Filter objects must not be destroyed when a FilterChain referencing them is destroyed.

Now, the two simplest ways to manage ownership in the FilterChain object are:

  • FilterChain own the Filter objects. This means the objects referenced by FilterChain are destroyed in FilterChain's destructor. Which is incompatible with scenario B.
  • FilterChain does not own the Filter objects. This means FilterChain's destructor does nothing. Now there is a problem with scenario A, because the user would have to know the internal structure of all the Filter objects involved in order to destroy them all without missing one, as the parent FilterChain does not do it itself. That's just bad design, and asking for memory leaks.

Consequently, I need something more complicated. My first guess is to design a smart pointer with a settable boolean flag indicating whether or not the smart pointer owns the object. Then instead of taking a collection of pointers to Filter objects, FilterChain would take a collection of smart pointers to Filter objects. When FilterChain's destructor is called, it would destroy the smart pointers. The destructor of the smart pointer itself would then destroy the object being pointed to (a Filter object) if and only if the boolean flag indicating ownership is set.

I get the feeling that this problem is commonplace in C++, but my web searches for popular solutions or clever design patterns were not very successful. Indeed, auto_ptr doesn't really help here and shared_ptr seems overkill. So, is my solution a good idea or not?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

虚拟世界 2024-09-19 01:13:45

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

使用 shared_ptr 将一次性解决大部分问题,并使您的设计变得更加简单。我认为这里唯一潜在的问题是你的过滤器碰巧包含循环。我认为如果你有某种反馈循环,这种情况就可能发生。在这种情况下,我建议将所有 Filter 对象归一个类所有,然后 FilterChain 将存储指向 Filter 对象的弱指针。

我敢打赌,过滤器阶段的执行时间将远远超过取消引用智能指针的额外开销。 shared_ptr 被设计得非常轻量。

Smart pointers here are not overkill: obviously you have a design problem that one way or another needs careful consideration of object lifetimes and ownership. This would be especially true if you want the ability to re-patch filters in the filter graph at runtime, or the ability to create compound FilterChain objects.

Using shared_ptr will remove most of those issues in one swoop and make your design a lot simpler. The only potential gotcha I think here is if your filter happens to contain cycles. I can see that could happen if you have some kind of feedback loop. In that instance I would suggest having all Filter objects owned by a single class, and then the FilterChain would store weak pointers to the Filter objects.

I would wager that the execution time of the filter stages would be far in excess of the extra overhead of dereferencing a smart pointer. shared_ptr is designed to be pretty lightweight.

屋檐 2024-09-19 01:13:45

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

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

编辑:正如尼尔指出的 Filter 需要一个虚拟析构函数。

Are filters so big that you can't simply make a deep copy of each one when you create the FilterChain? If you were able to do that, then all your problems disappear: The FilterChain always cleans up after itself.

If that's not an option due to memory concerns, then using shared_ptr seems to make the most sense. The caller will have to be responsible for keeping a shared_ptr for each object it cares about and then the FilterChain will know whether to delete particular filters or not when it is deleted.

EDIT: As Neil noted Filter needs a virtual destructor.

淡看悲欢离合 2024-09-19 01:13:45

FilterChain 应该有一个单独的DeleteAll() 方法,该方法对集合进行迭代并删除过滤器。它在场景 A 中被调用,而在场景 B 中被调用。这确实需要 FilterChain 用户的一些智慧,但只需要记住删除并反对他们新建。 (他们应该能够处理这个问题,否则他们就应该发生内存泄漏)

FilterChain should have a separate DeleteAll() method, which iterators over the collection and deletes the filters. It is called in Scenario A and is not called in Scenario B. This does require some intelligence on the part of the users of FilterChain, but no more then remembering to delete and object that they new'd. (They should be able to handle that, or they deserve a memeory leak)

少女的英雄梦 2024-09-19 01:13:45

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

I would go with FilterChain not owning the Filter objects. Then, in your library, when you need to load a FilterChain from the file you would have another Loader object that is responsible for the lifetime of the Filter objects. So, the FilterChain would work consistently for Chains loaded by the library and user created Chains.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文