Why is it fine to use Boost.Intrusive containers to store polymorphic objects?
这是一条经常重复的建议,即不要从具有非虚拟析构函数的类继承(如果目的是使用动态多态性的话)。这就是为什么从标准容器类继承被认为是一个坏主意的原因。
另一方面,boost.invigative明确地声明其容器适合存储多态对象。在链接的例子中,这是通过从没有虚拟析构函数的boost::intrusive::list_base_hook<>派生而来的,并且只在派生类中引入一个析构函数。
这是一个有效的设计吗?
如果是这样,它为什么和如何符合我上面提到的一般建议?我特别指出,为什么同样的逻辑不能证明从标准容器继承的合理性?
(注意,我的问题不是关于标准容器和Boost.侵入性容器之间的区别。我对boost的正确用法很感兴趣。这很有意思,但我只举标准容器为例,因为在讨论类似主题时,它们经常会出现。)
只有当使用派生类涉及通过指向基类的指针对对象的所有权时,才不应该从具有非虚拟析构函数的类继承。标准容器类没有什么特别的。从它们继承并不是一个坏主意,但问题是,扩展它们功能的其他方法应该是首选的:添加一个独立的函数或聚合。
从boost::intrusive::list_base_hook<>派生是完全有效的设计,因为派生对象的所有权从未通过指向list_base_hook的指针持有。请注意,库提供了通过聚合(使用list_member_hook进行连接),这应该比继承更好。
- 通过基类指针和多态破坏获得所有权。例如,std::shared_ptr类型在构造时擦除和存储指针的析构函数,因此不需要虚拟析构函数来工作。
- @不幸的是,在shared_ptr中没有魔力,它只能正确地接管指向派生类对象的指针的所有权。除非Base析构函数是虚拟的或提供了适当的删除程序,否则Base * const naked_p_base{new Derived{}}; ::std::shared_ptr p_base{naked_p_base};是中断的。
- 当然,但我确实说过"在结构上"——就像C++一样,你可以用你的方式去打破它,但是如果你像一个文明人那样使用EDCOX1 4,那么一切都会好起来的:
- 我知道会员挂钩。你为什么说它们应该比底钩更受欢迎?据我所见,文档中没有这样的内容。实际上,"使用成员挂钩"下面的最后一段似乎正好相反,特别是在多态性的上下文中。
- @关于构件挂钩,VTT应该优先于基础挂钩,但事实并非如此。成员钩子需要在boost.invigative中使用不可移植的实现,而对于大多数广泛使用的编译器,提供了工作实现(搜索offset_from_pointer_to_member),但它仍然不可移植,在某些情况下可能会中断。底钩没有这个问题,应该是便携式的。
- @Andreyesemashev这是一个有效的观点,但是它是一个适当实现的问题。
- @brown同一段落将在第一句话中使用成员挂钩的主要原因命名为:"有时列表挂钩和列表值类型之间不需要‘is-a’关系"。有时更应该是因为is-a是一种可能最强的关系类型。使用成员钩子的另一个动机是,当类包含多个钩子并且不会使类公共接口混乱时,它们倾向于简化事情。
- @VTT仅仅是将钩子作为成员的概念,不允许进行可移植的实现。C++中没有办法将指向成员的指针转换成指向包含对象的指针。
- @vtt:公共继承表示is-a关系。私有继承没有。