什么时候使用C++私有继承而不是组合?

Arm*_*ndo 12 c++ inheritance strategy-pattern composition private-inheritance

你能给我一个具体的例子,当我更喜欢使用私有继承而非组合时?就个人而言,我将使用组合而非私有继承,但可能存在这样的情况:使用私有继承是特定问题的最佳解决方案.阅读C++ faq,给出了一个使用私有继承的示例,但我似乎比私有继承更容易使用组合+策略模式甚至公共继承.

Cub*_*bbi 13

Scott Meyers在"Effective C++"第42项中说

"只有继承才能访问受保护的成员,只有继承允许重新定义虚函数.由于存在虚函数和受保护成员,私有继承有时是表达is-implemented-in-terms-of-between之间关系的唯一实用方法.类".


Mar*_*k B 7

private继承通常用于表示"按条件实现".我看到的主要用途是使用私有多重继承的mixin来构建具有各种mixin父项的适当功能的子对象.这也可以通过组合(我稍微偏好)来完成,但继承方法允许您using公开地公开一些父方法,并且在使用mixin方法时允许稍微更方便的表示法.


Ral*_*zky 5

私有继承接口

许多人忽视的私有继承的典型应用如下。

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;
};
Run Code Online (Sandbox Code Playgroud)

通常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;
    }
};
Run Code Online (Sandbox Code Playgroud)

然后,您可以在您感兴趣的类的相应操作的实现中使用该类的编译器生成的操作MyClassImpl。您可以对组合执行相同的操作,但这会使您的类的其余部分中的代码变得丑陋。如果您使用组合,则由于复制和移动操作的实现细节,其余的实现将会受到影响。私有继承避免了这种情况并避免了大量的代码重复。