【问题标题】:Why do we actually need Private or Protected inheritance in C++?为什么我们实际上需要 C++ 中的 Private 或 Protected 继承?
【发布时间】:2010-09-27 07:50:43
【问题描述】:

在 C++ 中,我想不出我想从 基类:

class Base;
class Derived1 : private Base;
class Derived2 : protected Base;

真的有用吗?

【问题讨论】:

  • 考虑一下:圆是椭圆但Circle is not substitutable for Ellipse,公共继承不是是is-a关系,虽然我们经常称它为所以。
  • 圆确实是一个椭圆。不明白你的意思?

标签: c++ inheritance private protected c++-faq


【解决方案1】:

私有继承最有可能是一种合法的设计策略 当您处理两个与 is-a 不相关的类时,其中一个 要么需要访问另一个受保护的成员,要么需要 重新定义其一个或多个虚函数。

来自 Scott Meyers Effective C++ 3rd Edition page in 191。

【讨论】:

    【解决方案2】:

    Private 在很多情况下都很有用。政策就是其中之一:

    Is partial class template specialization the answer to this design problem?.

    另一个有用的场合是禁止复制和分配:

    struct noncopyable {
        private:
        noncopyable(noncopyable const&);
        noncopyable & operator=(noncopyable const&);
    };
    
    class my_noncopyable_type : noncopyable {
        // ...
    };
    

    因为我们不希望用户拥有指向我们对象的noncopyable* 类型的指针,所以我们私下派生。这不仅适用于不可复制的类,也适用于许多其他此类(策略是最常见的)。

    【讨论】:

    • 无论您是公开还是私有地派生不可复制的,都无关紧要,因为无论如何复制构造函数和赋值运算符都是私有的。
    • 正如@litb 在他的回答中所说,私下派生可以防止用户使用指针或对不可复制对象的引用来引用 my_non_copyable_type 的实例。
    • 是的,这也可以防止用户通过指向不可复制的指针进行删除。
    • 如果我们想阻止接口或成员探索到第三个调用者。我们应该简单地保护基类成员和函数吗?
    【解决方案3】:

    私有继承主要用于错误的原因。正如前面的回答中所指出的,人们将它用于 IS-IMPLEMENTED-IN-TERMS-OF,但根据我的经验,保留副本而不是从类继承总是更干净。另一个较早的答案,关于 CBigArray 的答案,提供了这种反模式的完美示例。

    我意识到,由于过度使用“受保护”,has-a 可能无法正常工作,但修复损坏的类比损坏新的类要好。

    【讨论】:

    • 在许多情况下这是一个可行的选择。但是,如果您最终得到“可怕的死亡钻石”,那么编译器将无法帮助您(使用虚拟继承),您必须自己解决问题。
    【解决方案4】:

    当您想访问基类的某些成员但又不想在类接口中公开它们时,它很有用。私有继承也可以看作是某种组合:C++ faq-lite 给出了下面的例子来说明这种说法

    class Engine {
     public:
       Engine(int numCylinders);
       void start();                 // Starts this Engine
    };
    
    class Car {
      public:
        Car() : e_(8) { }             // Initializes this Car with 8 cylinders
        void start() { e_.start(); }  // Start this Car by starting its Engine
      private:
        Engine e_;                    // Car has-a Engine
    };
    

    为了获得相同的语义,您还可以编写汽车类如下:

    class Car : private Engine {    // Car has-a Engine
     public:
       Car() : Engine(8) { }         // Initializes this Car with 8 cylinders
       using Engine::start;          // Start this Car by starting its Engine
    }; 
    

    但是,这种做法有几个缺点:

    • 你的意图不太清楚
    • 它可能导致滥用多重继承
    • 它破坏了 Engine 类的封装,因为您可以访问其受保护的成员
    • 您可以覆盖 Engine 虚拟方法,如果您的目标是简单的合成,这是您不想要的东西

    【讨论】:

    • 在我看来,在“has a”的情况下,不应该有任何继承,但应该有一个组合。我认为这是滥用继承的一个坏例子,因为它让用户感到困惑。
    • 第二个例子是一个非常糟糕的例子,否则你最终会变成这样:class Car : private Engine, private Wheel, private Seat
    • 是的,“has-a”不应该是继承。我将私有继承用于“is-implemented-in-temrs-of”。这实际上意味着“is-a”,但作为一种 hack。就像你重用了一段本不应该产生这些后代的代码,但是嘿......
    【解决方案5】:

    我曾经将这些数据结构实现为类:

    • 链接列表
    • 通用数组(抽象)
    • 简单数组(从泛型数组继承)
    • 大数组(从泛型数组继承)

    大数组的接口让它看起来像一个数组,然而,它实际上是一个固定大小的简单数组的链表。所以我这样声明:

    template <typename T>
    class CBigArray : public IArray, private CLnkList {
        // ...
    

    【讨论】:

    • 这是一个很好的例子,说明如何不使用私有继承。
    【解决方案6】:

    我曾在某一时刻同时使用私有继承和受保护继承。

    当您希望某些东西具有基类的行为,然后能够覆盖该功能,但您不希望全世界都知道并使用它时,私有继承很有用。您仍然可以通过让函数返回该接口来使用私有派生类的接口。当您可以让事物注册自己以侦听回调时,它也很有用,因为它们可以使用私有接口注册自己。

    当您的基类从另一个类派生有用功能但您只希望其派生类能够使用它时,受保护的继承特别有用。

    【讨论】:

      【解决方案7】:

      例如,当您想要重用实现而不是类的接口并覆盖其虚函数时。

      【讨论】:

        【解决方案8】:

        公共继承模型 IS-A。
        非公共继承模型 IS-IMPLEMENTED-IN-TERMS-OF。
        遏制模型HAS-A,相当于IS-IMPLEMENTED-IN-TERMS-OF。

        Sutter on the topic。他解释了您何时会选择非公共继承而不是包含实现细节。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-06-14
          • 1970-01-01
          • 1970-01-01
          • 2020-08-22
          • 2012-04-27
          • 2011-06-17
          • 1970-01-01
          相关资源
          最近更新 更多