何时使用C++私有继承而不是组合?

19

您能给我举一个明确的例子,说明何时使用私有继承比组合更好?个人而言,我会优先选择组合而非私有继承,但在某些情况下,使用私有继承可能是解决特定问题的最佳方案。阅读C++ faq可以给出使用私有继承的例子,但似乎使用组合+策略模式或者甚至公共继承都比私有继承更加容易。


3
这个问题已经有一百万个重复的版本了,请搜索一下。而且,正如你所说,你已经找到了一个例子。仅仅因为你不喜欢它并不意味着它不是一个例子... - Lightness Races in Orbit
请参见:https://dev59.com/llfUa4cB1Zd3GeqPDwSU - Maxpm
我认为“从不”是正确的答案... - Santiago V.
1
谢谢,我搜索了一下,但是使用标题字符串没有显示任何内容,不过我会使用模板递归模式来解决这个问题。 - Armando
请参见 https://dev59.com/KXVD5IYBdhLWcg3wNY5Z - Raedwald
3个回答

17

Scott Meyers在《Effective C++》第42条中说:

"只有继承才能访问受保护的成员,也只有继承才允许重定义虚函数。由于虚函数和受保护的成员存在,所以私有继承有时是表达类之间实现关系的唯一实用方法。"


8

private继承通常用于表示“基于实现”。我看到的主要用途是使用私有多重继承的mixin来构建具有来自各种mixin父类的适当功能的子对象。这也可以使用组合(我稍微更喜欢)完成,但继承方法允许您使用using公开一些父方法,并在使用mixin方法时允许稍微更方便的符号。


你真厉害!我明白为什么私有继承可能会导致一个更简单的解决方案...谢谢! - Armando

4

私有继承接口

许多人忽视的私有继承的典型应用是以下情况。

在IT技术中,私有继承可以用于隐藏实现细节并限制对基类接口的公共访问。这样,派生类只能通过调用基类的公共成员函数来访问其接口。

class InterfaceForComponent
{
public:
    virtual ~InterfaceForComponent() {}
    virtual doSomething() = 0;
};

class Component
{
public:
    Component( InterfaceForComponent * bigOne ) : bigOne(bigOne) {}

    /* ... more functions ... */

private:
    InterfaceForComponent * bigOne;
};

class BigOne : private InterfaceForComponent
{
public:
    BigOne() : component(this) {}

    /* ... more functions ... */

private:
    // implementation of InterfaceForComponent
    virtual doSomething();

    Component component;
};

通常情况下,BigOne是一个承担许多职责的类。为了将代码模块化,您需要将代码分解为组件,这些组件可以帮助处理一些小事情。这些组件不应该是 BigOne 的朋友,但它们仍然可能需要访问你不想公开的类的某些内容,因为这些是实现细节。因此,您需要为此组件创建一个接口来提供受限制的访问。这使得您的代码更易于维护和理解,因为事物有明确的访问边界。
我在一个几年的项目中经常使用这种技术,并且效果很好。这里不适用组合。
让编译器生成部分复制构造函数和赋值
有时候,有一些可复制/可移动的类具有许多不同的数据成员。编译器生成的复制或移动构造函数和赋值通常都是可以的,只有一两个数据成员需要特殊处理。如果频繁添加、删除或更改数据成员,手写的复制和移动构造函数和赋值就需要每次更新,这会产生代码膨胀,使类更难维护。
解决方法是将那些可以进行编译器生成的复制和移动操作的数据成员封装到一个额外的structclass中,并进行私有继承。
struct MyClassImpl
{
    int i;
    float f;
    double d;
    char c;
    std::string s;
    // lots of data members which can be copied/moved by the 
    // compiler-generated constructors and assignment operators. 
};

class MyClass : private MyClassImpl
{
public:
    MyClass( const MyClass & other ) : MyClassImpl( other )
    {
        initData()
    }

    MyClass( MyClass && other ) : MyClassImpl( std::move(other) )
    {
        initData()
    }

    // and so forth ...

private:
    int * pi;

    void initData()
    {
        pi = &p;
    }
};

您可以使用MyClassImpl 类的编译器生成操作来实现您感兴趣的类的各自操作。您也可以通过组合方式实现,但这会使您的代码在类的其他部分变得混乱。如果您使用组合,则由于复制和移动操作的实现细节,整个实现将受到影响。私有继承可避免此问题,并避免大量的代码重复。


非常有趣的技巧,虽然我不确定我完全理解了这个技巧,能否详细说明一下并添加一个实际运行的代码呢?如果您能提供一个可以在http://cpp.sh/上运行的示例,那将非常酷。 - InsaneBot
@BinaryA 麻烦在于很难找到一个合理的代码示例,因为代码需要一定的复杂性才能使模式合理。对我来说,使用案例是GUI代码,其中我有一个小部件类,它具有大量功能。这些功能被封装成子组件,每个组件可以通过不同的接口与小部件进行通信。小部件类私有继承这些接口并实现它们。 - Ralph Tandetzky
我只是想看看调用栈/序列是如何执行的,例如:~InterfaceForChild()有什么用?为什么前面要加上~?客户端初始化和调用哪个类? - InsaneBot
@BinaryA 这似乎是一个打字错误,可能应该是 ~InterfaceForComponent - Aidiakapi
@Aidiakapi 谢谢你的提示。已经修复了。 - Ralph Tandetzky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接