【发布时间】:2016-05-21 11:49:44
【问题描述】:
我经常遇到的一种情况是有一组类Base 和Derived,其中Base 类拥有基类成员BaseMember 的所有权,而Derived 类拥有指向同一对象的引用或指针,但作为 DerivedMember。
例如,一个 UI 面板类,它包含具有某些特殊控件功能的某种类型控件的特定实例,继承自包含通用控件并具有通用控件功能的通用类。
首先,假设BaseMember 被DerivedMemeber 继承。
如果不使用智能指针,我可能会这样做:
class Base
{
protected:
// receive ownership but only because we say so,
// someone else can still try to delete as it's "just a pointer"
Base(BaseMember* _bmember):
bmember(_bmember)
{}
public:
virtual ~Base()
{
// perform an owner's duty
delete bmember;
}
// functions that might be based on BaseMember + other base state
void SetMemberId(....)
{
bmember->SetId(baz);
}
private:
int baz;
BaseMember* bmember; //owned, but not smartly
}
class Derived: public Base
{
public:
Derived(DerivedMember* _dmember):
Base(_dmember),
dmember(_dmember)
{}
// functions that only make sense for Derived + Derived/Base state
void SetDerivedFrobulation()
{
// only a DerivedMember has frobulation, so only
// Derived allows users to access it
dmember->setFrobulation(foo);
}
private:
int foo; // some state
DerivedMember* dmember; // no ownership here
}
使用智能指针(特别是 C++11 及更高版本,在这种情况下,我并不真正关心较旧的 C++),我很想做这样的事情,并且永远不要让 Base/DerivedMember 对象变得愚蠢-如果在不方便的地方出现异常,它可能会泄漏的指针区域。
class Base
{
protected:
// receive ownership
Base(std::unique_ptr<BaseMember> _member):
member(std::move(_member))
{}
virtual ~Base()
{}
public:
// public access functions here as before
private:
std::unique_ptr<BaseMember> member;
}
class Derived: public Base
{
public:
// pass the ownership down by unique_ptr
Derived(std::unique_ptr<DerivedMember> _dmember):
Base(std::move(_dmember)),
dmember(_dmember.get()) // _dmember is moved! SEGFAULT if access dmember later!
{}
// public access functions here as before
private:
// handy handle to the derived class so we don't need to downcast the base (or even access it!)
DerivedClass* dmember
}
正如我在那里指出的,你不能“偷看”DerivedMember 类,因为它进入了 Derived 构造函数,因为在 Derived 获取之前,unique_ptr 是 moved进去看看。
我可以看到在Derived 构造函数中提供protected 访问BaseMember 和static_cast 回到DerivedMember 的解决方案(即在Base 构造函数完成之后),但是这个似乎是一种丑陋的方式来访问我们从手指间溜走的变量!
另一种方式可能是Base 的每个继承者都拥有指针,而 base 只是得到一个哑指针。在这种情况下,Base 析构函数无法访问该成员,因为它已经消失了。它还会不必要地复制所有权逻辑。
我认为:
- 这是反模式的症状,整个
Base/Derived/BaseMember/DerivedMember系统的设计不是好的做法。 - 我错过了一个技巧,有一种干净的方法可以做到这一点,而无需摸索智能指针并导致泄漏或添加函数和暴露接口或转换太多。
这是一个很好的重复使用模式,还是我应该看看别处?
扩展用例(编辑)
在核心库中,我有一个类 DataInterpreter,它显示了数据的“某种解释”——可以是字符串、图像等。然后由 TextInterpreter 等继承,它提供了一个 @ 987654351@.
然后我有一个DataDisplayPanel 类,它代表一个用于以抽象意义显示的 UI。此面板中的确切内容将取决于所使用的解释器:TextInterpreter 应该获得一个文本输入字段并说出一个按钮来设置一些文本显示选项,这在 TextDisplayPanel 中处理,它具有“特殊”知识解释器的文本方面。
然后有一个DataAggregatePanel,它结合了许多DataDisplayPanels,并提供了一些影响所有显示的全局设置(通过虚拟功能),并在std::vector<std::unique_ptr<DataDisplayPanel> > 中管理面板。这个聚合类根本不处理任何派生类,任何函数都是多态的并在基类中定义。
在应用程序(依赖于核心库)中,这些类被扩展(通过继承或组合,以更有意义的方式为准)。例如,如果应用程序是 WX GUI,我可能有wxDataAggregatePanel,其中包含wxTextDisplayPanel(和其他),所有这些都是wxPanels。在这种情况下,wxTextDisplayPanel 可能拥有wxTextEntry,并且拥有或继承TextInterpreter,并使用其对TextInterpreter 的特定方法的了解来用字符串填充文本框。
【问题讨论】:
-
对兴趣点赞。我过去曾遇到过这种(反?)模式,但谢天谢地,最近(并且看似无限)对该项目的重写已经使整个架构成倍地减少了代码异味,这意味着我不需要考虑它是正确的现在......但我仍然对我过去提出的问题的答案感兴趣。我也刚刚意识到这一切似乎与 PImpl 习惯用法和实现类之间的继承高度相关。
-
我认为这是一种反模式。感觉就像您正在关联不应该像那样关联的东西。您能否进一步扩展用例?
-
在您的第一个示例中,您通过存储两个指向同一个对象的指针来浪费内存。如果你需要
Base是一个真正的多态,你可能有私有的virtual BaseMember *getMember() = 0并且只在Derived对象中存储DerivedMember*。如果Base仅作为其他类的通用实现,您可以将其作为模板(参见 CRTP)并将DerivedMember*存储在Base<DerivedMember>对象中。
标签: c++ c++11 inheritance smart-pointers raii