如何处理指针成员的不同所有权策略?
Posted
技术标签:
【中文标题】如何处理指针成员的不同所有权策略?【英文标题】:How to deal with different ownership strategies for a pointer member? 【发布时间】:2011-03-22 08:25:33 【问题描述】:考虑以下类结构:
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
的两种可能的使用场景:
FilterChain
对象。例如,其中一个函数从一个文件构造一个过滤器链,可以描述任意复杂的过滤器(包括过滤器链的过滤器链等)。工作完成后,函数的用户负责对象销毁。
场景 B:用户可以访问一堆Filter
对象,并希望以特定方式将它们组合到过滤器链中。用户构造FilterChain
对象供自己使用,然后在完成后销毁它们。当引用它们的 FilterChain
被销毁时,Filter
对象不得被销毁。
现在,在FilterChain
对象中管理所有权的两种最简单方法是:
FilterChain
拥有 Filter
对象。这意味着FilterChain
引用的对象在FilterChain
的析构函数中被销毁。这与场景 B 不兼容。
FilterChain
不拥有Filter
对象。这意味着FilterChain
的析构函数什么都不做。现在方案 A 存在问题,因为用户必须知道所涉及的所有 Filter
对象的内部结构才能将它们全部销毁而不会丢失任何一个,因为父 FilterChain
自己不会这样做。这只是糟糕的设计,并要求内存泄漏。
因此,我需要一些更复杂的东西。我的第一个猜测是设计一个带有可设置布尔标志的智能指针,指示智能指针是否拥有该对象。然后,FilterChain
将不采用指向 Filter
对象的指针集合,而是采用指向 Filter
对象的智能指针集合。当FilterChain
的析构函数被调用时,它会破坏智能指针。然后,智能指针本身的析构函数将销毁指向的对象(Filter
对象)当且仅当设置了指示所有权的布尔标志。
我觉得这个问题在 C++ 中很常见,但是我在网上搜索流行的解决方案或聪明的设计模式并不是很成功。事实上,auto_ptr
在这里并没有真正的帮助,shared_ptr
似乎有点矫枉过正。那么,我的解决方案是不是一个好主意?
【问题讨论】:
为什么共享指针是多余的?在立即实施和维护方面比自己动手要容易得多(每个人都知道它们是什么)。开销很小。 基类析构函数必须是虚拟的。 @Patrick:对我来说感觉有点矫枉过正,因为 shared_ptr 是一个引用计数器,我只需要一个布尔标志。 @Neil Butterworth:是的,当然。为了简洁起见,我省略了它。 但是编写和测试一个完整的智能指针类不是比使用一个使用 int 而不是 bool 的预测试类更过分吗?也有理由认为 std::shared_ptr 实现会比我第一次尝试智能指针类更好(代码大小、速度等)...... 【参考方案1】:这里的智能指针并不过分:显然你有一个设计问题,需要仔细考虑对象的生命周期和所有权。如果您希望能够在运行时重新修补过滤器图中的过滤器,或者能够创建复合 FilterChain
对象,则尤其如此。
使用shared_ptr
将一举解决大部分问题,并使您的设计更加简单。我认为这里唯一潜在的问题是您的过滤器是否恰好包含循环。如果您有某种反馈循环,我可以看到这可能会发生。在这种情况下,我建议将所有Filter
对象归一个类所有,然后FilterChain
将存储指向Filter
对象的弱指针。
我敢打赌,过滤阶段的执行时间将远远超过取消引用智能指针的额外开销。 shared_ptr
的设计非常轻巧。
【讨论】:
似乎这里的每个人都认为这是正确的做法。好吧,那我们去shared_ptr吧。 我曾经在谈到智能指针时有一点心理障碍,但最近开始使用 shared_ptr 我发现它让生活变得如此更容易。【参考方案2】:过滤器是否如此之大,以至于您在创建FilterChain
时不能简单地对每个过滤器进行深层复制?如果您能够做到这一点,那么您的所有问题都会消失:FilterChain
总是会自行清理。
如果由于内存问题这不是一个选项,那么使用shared_ptr
似乎是最有意义的。调用者必须负责为它关心的每个对象保留一个shared_ptr
,然后FilterChain
将知道是否删除特定的过滤器,当它是delete
d。
编辑:正如 Neil 所说,Filter
需要一个虚拟析构函数。
【讨论】:
过滤器对象是不可复制的,不是因为内存问题,而是因为它会导致与状态信息重复相关的问题(而且,它在语义上是可疑的)。 @e-t172 那么将过滤器状态存储在shared_ptr
中以便您可以复制Filter
s 呢?
FilterChain中使用shared_ptr有什么区别?
@e-t172 它只是改变了引用计数发生的位置。如果您允许Filter
对象具有引用计数状态,那么您可以随意复制它们,完全无需客户端创建shared_ptr
到过滤器:他们只需传入集合并理解他们仍然拥有集合中的所有过滤器,并且Filtercontainer
将在完成后正确清理其所有过滤器。【参考方案3】:
FilterChain
应该有一个单独的 DeleteAll()
方法,它遍历集合和 delete
s 过滤器。它在场景 A 中被调用,而在场景 B 中不被调用。这确实需要 FilterChain 用户的一些智能,但只需要记住 @987654324 @并反对他们new
'd。 (他们应该能够处理,否则他们应该得到内存泄漏)
【讨论】:
他们不应该有记得在任何地方删除任何内容。使用容器、智能指针等。保持异常安全、干净,从不显式管理实用程序类之外的东西。【参考方案4】:我会选择不拥有 Filter 对象的 FilterChain。然后,在您的库中,当您需要从文件中加载 FilterChain 时,您将拥有另一个 Loader 对象,该对象负责 Filter 对象的生命周期。因此,FilterChain 将始终适用于库加载的链和用户创建的链。
【讨论】:
这意味着将可能复杂的过滤器结构复制到 Loader 对象中,仅用于管理所有权。与其他解决方案相比,这过于复杂。 事实上,您提出的只是我最初问题中的第二个“解决方案”,除了您将管理过滤器对象的逻辑从库外部移动到内部。确实,对于库的用户来说,代码更简单,但仅仅因为过度的复杂性隐藏在库中并不意味着它是好的设计(这是必要的,但还不够)。 我不明白为什么需要在加载程序中复制过滤器结构。加载器只需要管理过滤器的生命周期。图书馆的用户仍然需要管理他们自己的过滤器的生命周期。过滤器应该(反)序列化自己。每当我看到这样的代码时,都是因为在 Filter 类的设计中没有足够的考虑,现在你付出了代价。 “我不明白为什么你需要在加载器中复制过滤器结构。加载器只需要管理过滤器的生命周期。”我同意。问题是,一个过滤器可以是一个简单的过滤器,也可以是一个过滤器链,或者一个链链等等,而我们刚刚回到了最初的问题。以上是关于如何处理指针成员的不同所有权策略?的主要内容,如果未能解决你的问题,请参考以下文章