什么时候应该使用C ++私有inheritance?

与受保护的inheritance不同,C ++私有inheritance发现了进入主streamC ++开发的方式。 但是,我仍然没有find一个很好的用处。

你们什么时候用它?

答案接受后注意: 这不是一个完整的答案。 如果您对这个问题感兴趣,请阅读这里 (概念上)和这里 (理论和实践)的其他答案。 这只是私人inheritance可以实现的一个奇妙的技巧。 虽然它是幻想, 这不是问题的答案。

除了C ++ FAQ(链接在其他的注释)中显示的私有inheritance的基本用法之外,您可以使用私有inheritance和虚拟inheritance的组合来封装类(使用.NET术语)或最终形成类(使用Java术语) 。 这不是一个常见的用途,但无论如何,我觉得它很有趣:

class ClassSealer { private: friend class Sealed; ClassSealer() {} }; class Sealed : private virtual ClassSealer { // ... }; class FailsToDerive : public Sealed { // Cannot be instantiated }; 

密封可以被实例化。 它来自ClassSealer ,可以直接调用私有构造函数,因为它是一个朋友。

FailsToDerive不会编译,因为它必须直接调用ClassSealer构造函数(虚拟inheritance要求),但它不能像在Sealed类中是私有的,在这种情况下, FailsToDerive不是ClassSealer的朋友。


编辑

在评论中提到,在使用CRTP的时候,这是不能通用的。 C ++ 11标准通过提供不同的语法来帮助模板参数来消除这种限制:

 template <typename T> class Seal { friend T; // not: friend class T!!! Seal() {} }; class Sealed : private virtual Seal<Sealed> // ... 

当然,这是毫无意义的,因为C ++ 11完全为此提供了一个final上下文关键字:

 class Sealed final // ... 

我一直使用它。 我的头顶上有几个例子:

  • 当我想要公开一些但不是全部的基类的接口。 公开inheritance将是一个谎言,因为Liskov可替代性被打破,而组成将意味着写一堆转发function。
  • 当我想从没有虚拟析构函数的具体类派生。 公共inheritance将邀请客户端通过指针到基地进行删除,调用未定义的行为。

一个典型的例子就是从STL容器中私下导出:

 class MyVector : private vector<int> { public: // Using declarations expose the few functions my clients need // without a load of forwarding functions. using vector<int>::push_back; // etc... }; 
  • 当实现适配器模式时,从Adapted类私下inheritance将不必转发到一个封闭的实例。
  • 实现一个私人界面 。 这通常与观察者模式。 通常我的观察者类,MyClass说, 订阅自己与一些主题。 然后,只有MyClass需要执行MyClass – > Observer转换。 系统的其余部分不需要知道,所以表示私有inheritance。

私有inheritance的规范使用是“按照”关系实现的(感谢Scott Meyers的“Effective C ++”)。 换句话说,inheritance类的外部接口与inheritance类没有(可见)关系,但是它在内部使用它来实现它的function。

我认为C ++ FAQ Lite的关键部分是:

对于私有inheritance来说,合法的,长期的使用是,当你想要构build一个在类Wilma中使用代码的类Fred时,需要从你的新类Fred调用成员函数。 在这种情况下,弗雷德称威尔玛是非虚拟的,而威尔玛本身称为(通常是纯虚拟的),弗雷德则忽略了这一点。 这对于构图来说要困难得多。

如果有疑问,你应该比私人inheritance更喜欢构图。

私有inheritance的一个有用的用法是当你有一个实现了一个接口的类,然后在其他对象上注册。 您将该接口设置为私有的,以便该类本身必须注册,并且只有注册的特定对象才能使用这些function。

例如:

 class FooInterface { public: virtual void DoSomething() = 0; }; class FooUser { public: bool RegisterFooInterface(FooInterface* aInterface); }; class FooImplementer : private FooInterface { public: explicit FooImplementer(FooUser& aUser) { aUser.RegisterFooInterface(this); } private: virtual void DoSomething() { ... } }; 

因此,FooUser类可以通过FooInterface接口调用FooImplementer的私有方法,而其他外部类不能。 这是处理被定义为接口的特定callback的一个很好的模式。

我发现它对inheritance的接口(即抽象类)很有用,我不希望其他代码触及接口(只有inheritance类)。

[在一个例子中编辑]

以上面链接的例子 。 说这个

Wilma类需要从你的新类Fred中调用成员函数。

就是说,威尔玛要求弗雷德能够引用某些成员函数,或者说, 威尔玛是一个接口 。 因此,正如在例子中提到的那样

私人inheritance不是邪恶的; 维护起来更加昂贵,因为它增加了某个人改变会破坏你的代码的可能性。

程序员需要满足我们的接口要求或打破代码的预期效果的评论。 而且,由于fredCallsWilma()是受保护的,只有朋友和派生类可以触及它,即只有inheritance类可以触及(和朋友)的inheritance接口(抽象类)。

[在另一个例子中编辑]

本页简要讨论了私有接口(从另一个angular度)。

有时我发现使用私有inheritance是有用的,当我想在另一个接口中暴露一个较小的接口(例如一个集合)时,集合实现需要访问暴露类的状态,类似于内部类Java的。

 class BigClass; struct SomeCollection { iterator begin(); iterator end(); }; class BigClass : private SomeCollection { friend struct SomeCollection; SomeCollection &GetThings() { return *this; } }; 

那么如果SomeCollection需要访问BigClass,它可以static_cast<BigClass *>(this) 。 不需要额外的数据成员占用空间。

如果派生类 – 需要重用代码,并且 – 您不能更改基类,并且 – 使用基础成员在锁下保护其方法。

那么你应该使用私有inheritance,否则你有通过这个派生类导出的基本方法解锁的危险。

有时它可能是聚合的替代方法,例如,如果您想要聚合,但是聚合实体的行为发生变化(覆盖虚拟function)。

但是你是对的,现实世界中并没有太多例子。

当关系不是“是”时使用的私有inheritance,但是新类可以是“现有类的实现”或新的类“现有类的工作”。

例如来自“Andrei Alexandrescu,Herb Sutter的C ++编码标准”的例子: – 考虑两个类Square和Rectangle每个都有虚拟函数来设置它们的高度和宽度。 然后Square不能正确地从Rectangleinheritance,因为使用可修改的Rectangle的代码将假定SetWidth不会改变高度(不pipeRectangle是否明确地logging该合同),而Square :: SetWidth不能保存该合约和它自己的矩形不变性同一时间。 但是,如果Square的客户端假定Square的面积是其宽度的平方,或者如果他们依赖于其他一些不适用于Rectangles的属性,矩形也不能正确地从Squareinheritance。

一个正方形的“is-a”矩形(math),但一个正方形不是一个矩形(行为上)。 因此,我们宁愿说“作品一样”(或者,如果你愿意的话,“可用作为一个”),而不是“is-a”,以使描述不太容易产生误解。

一个类拥有一个不variables。 不variables由构造函数build立。 但是,在许多情况下,查看对象的表示状态(可以通过networking传输或保存到文件 – 如果您愿意,可以使用DTO)很有用。 REST最好用AggregateType来完成。 如果你是常量正确的,这是尤其如此。 考虑:

 struct QuadraticEquationState { const double a; const double b; const double c; // named ctors so aggregate construction is available, // which is the default usage pattern // add your favourite ctors - throwing, try, cps static QuadraticEquationState read(std::istream& is); static std::optional<QuadraticEquationState> try_read(std::istream& is); template<typename Then, typename Else> static std::common_type< decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()), decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes) if_read(std::istream& is, Then then, Else els); }; // this works with QuadraticEquation as well by default std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes); // no operator>> as we're const correct. // we _might_ (not necessarily want) operator>> for optional<qes> std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>); struct QuadraticEquationCache { mutable std::optional<double> determinant_cache; mutable std::optional<double> x1_cache; mutable std::optional<double> x2_cache; mutable std::optional<double> sum_of_x12_cache; }; class QuadraticEquation : public QuadraticEquationState, // private if base is non-const private QuadraticEquationCache { public: QuadraticEquation(QuadraticEquationState); // in general, might throw QuadraticEquation(const double a, const double b, const double c); QuadraticEquation(const std::string& str); QuadraticEquation(const ExpressionTree& str); // might throw } 

此时,您可能只需将caching集合存储在容器中,然后在构build时查看它。 方便,如果有一些真正的处理。 请注意,caching是QE的一部分:在QE上定义的操作可能意味着caching部分可重用(例如,c不会影响总和); 但是,当没有caching的时候,值得查看它。

私有inheritance几乎总是由成员build模(如果需要,存储对基的引用)。 以这种方式build模并不总是值得的; 有时inheritance是最有效的表示。

仅仅因为C ++有一个特性,并不意味着它是有用的或者应该被使用。

我会说你不应该使用它。

无论如何,如果你正在使用它,那么你基本上是在违反封装,降低凝聚力。 您将数据放在一个类中,然后添加方法来处理另一个类中的数据。

像其他C ++特性一样,它可以用来实现封闭类(如dribeas的答案中提到的)的副作用,但是这并不是一个好的特性。