与受保护的继承不同,C++私有继承进入主流C++开发.但是,我仍然没有找到它的好用.
你什么时候使用它?
我用它所有的时间.我头顶的几个例子:
当我想暴露一些但不是全部基类的接口时.公共继承将是一个谎言,因为Liskov可替代性被打破,而组合意味着编写一堆转发函数.
当我想从没有虚拟析构函数的具体类派生时.公共继承将邀请客户端通过指向base的指针进行删除,从而调用未定义的行为.
一个典型的例子是私下从STL容器派生:
class MyVector : private vector{ public: // Using declarations expose the few functions my clients need // without a load of forwarding functions. using vector ::push_back; // etc... };
实现适配器模式时,从Adapted类私下继承可以节省转发到封闭实例的过程.
实现私有接口.这经常出现在观察者模式中.通常我的Observer类,MyClass说,订阅自己的一些主题.然后,只有MyClass需要进行MyClass - > Observer转换.系统的其余部分不需要知道它,因此指示了私有继承.
回答接受后请注意:这不是一个完整的答案.如果您对这个问题感兴趣,请阅读此处(概念)和此处(理论和实践)等其他答案.这只是一个可以通过私有继承实现的花哨技巧.虽然它很花哨,但它不是问题的答案.
除了C++ FAQ中显示的私有继承的基本用法(在其他注释中链接),您可以使用私有和虚拟继承的组合来密封类(在.NET术语中)或使类最终(在Java术语中) .这不是常用的,但无论如何我发现它很有趣:
class ClassSealer { private: friend class Sealed; ClassSealer() {} }; class Sealed : private virtual ClassSealer { // ... }; class FailsToDerive : public Sealed { // Cannot be instantiated };
密封可以实例化.它派生自ClassSealer,可以直接调用私有构造函数,因为它是朋友.
FailsToDerive将无法编译,因为它必须调用ClassSealer直接构造(虚拟继承的要求),但它不能,因为它是在私人密封类,并在这种情况下FailsToDerive不是朋友ClassSealer.
编辑
评论中提到,当时使用CRTP不能使其成为通用的.C++ 11标准通过为befriend模板参数提供不同的语法来消除该限制:
templateclass Seal { friend T; // not: friend class T!!! Seal() {} }; class Sealed : private virtual Seal // ...
当然,这一切都没有实际意义,因为C++ 11 final
为此目的提供了一个上下文关键字:
class Sealed final // ...
私有继承的规范用法是"以"关系实现"(感谢Scott Meyers的'Effective C++'这个措辞).换句话说,继承类的外部接口与继承类没有(可见)关系,但它在内部使用它来实现其功能.
私有继承的一个有用用途是当你有一个实现接口的类,然后在其他对象中注册.您将该接口设置为私有,以便类本身必须注册,并且只有其注册的特定对象才能使用这些功能.
例如:
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的私有方法,而其他外部类则不能.这是处理定义为接口的特定回调的一种很好的模式.
我认为C++ FAQ Lite的关键部分是:
私有继承的合法,长期使用是当你想要构建一个使用类Wilma中的代码的Fred时,来自Wilma类的代码需要从你的新类Fred调用成员函数.在这种情况下,Fred在Wilma中调用非虚拟,而Wilma调用(通常是纯虚拟)本身,由Fred重写.这对组合来说要困难得多.
如果有疑问,你应该更喜欢组合而不是私人继承.