如何处理指针成员的不同所有权策略?
考虑以下类结构:
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 constructsFilterChain
objects for its own use, then destroy them when he's done with them. TheFilter
objects must not be destroyed when aFilterChain
referencing them is destroyed.
Now, the two simplest ways to manage ownership in the FilterChain
object are:
FilterChain
own theFilter
objects. This means the objects referenced byFilterChain
are destroyed inFilterChain
's destructor. Which is incompatible with scenario B.FilterChain
does not own theFilter
objects. This meansFilterChain
's destructor does nothing. Now there is a problem with scenario A, because the user would have to know the internal structure of all theFilter
objects involved in order to destroy them all without missing one, as the parentFilterChain
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这里的智能指针并不过分:显然你有一个设计问题,需要以某种方式仔细考虑对象的生命周期和所有权。如果您希望能够在运行时重新修补过滤器图中的过滤器,或者能够创建复合 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 allFilter
objects owned by a single class, and then theFilterChain
would store weak pointers to theFilter
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.过滤器是否太大以至于您在创建
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: TheFilterChain
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 ashared_ptr
for each object it cares about and then theFilterChain
will know whether to delete particular filters or not when it isdelete
d.EDIT: As Neil noted
Filter
needs a virtual destructor.FilterChain 应该有一个单独的DeleteAll() 方法,该方法对集合进行迭代并删除过滤器。它在场景 A 中被调用,而在场景 B 中不被调用。这确实需要 FilterChain 用户的一些智慧,但只需要记住
删除
并反对他们新建
。 (他们应该能够处理这个问题,否则他们就应该发生内存泄漏)FilterChain
should have a separateDeleteAll()
method, which iterators over the collection anddelete
s 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 todelete
and object that theynew
'd. (They should be able to handle that, or they deserve a memeory leak)我会选择不拥有 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.