公有继承、私有继承和保护继承之间有什么区别?

1243
在C++中,publicprivateprotected继承有什么区别?
17个回答

1757
class A 
{
    public:
       int x;
    protected:
       int y;
    private:
       int z;
};

class B : public A
{
    // x is public
    // y is protected
    // z is not accessible from B
};

class C : protected A
{
    // x is protected
    // y is protected
    // z is not accessible from C
};

class D : private A    // 'private' is default for classes
{
    // x is private
    // y is private
    // z is not accessible from D
};

重要提示:B类、C类和D类都包含变量x、y和z,只是访问的问题。

关于使用protected和private继承,您可以在这里阅读。


49
Anzurio所写的内容只有与你下面的回答结合起来才有意义。Плус 1. - Iwillnotexist Idonotexist
3
我对这个工作方式的理解完全错误了!非常感谢您的澄清。 - tjwrona1992
1
我花了一些时间才明白这个。但现在很清楚了。谢谢! - Chan Kim
3
关于类默认为“私有”的好备注。 - John Leuenhagen
1
讲解得非常好。到目前为止,这里的一切都很清晰。 - Badhan Sen
显示剩余2条评论

1196

为了回答这个问题,我想用自己的话来描述成员访问器。如果你已经知道这个,可以跳到“下一步”标题。

我知道有三个访问器:publicprotectedprivate

假设:

class Base {
    public:
        int publicMember;
    protected:
        int protectedMember;
    private:
        int privateMember;
};
  • 所有知道Base的东西也知道Base包含publicMember
  • 只有子类(以及它们的子类)知道Base包含protectedMember
  • 除了Base之外,没有人知道privateMember

“知道”的意思是“承认其存在,并能够访问”。

接下来:

公共、私有和保护继承同样适用。假设有一个类Base和一个从Base继承的类Child

  • 如果继承是public的,则所有知道BaseChild的东西也都知道Child继承自Base
  • 如果继承是protected的,则只有Child及其子类知道它们继承自Base
  • 如果继承是private的,则除了Child之外,没有其他人知道这种继承关系。

224
我想补充一点,C++中的可见性是基于类而不是对象的,这意味着同一类的对象可以自由访问彼此的私有字段。 - Zhe Chen
59
如果你很难理解这个问题,请先阅读Kirill V. Lyadvinsky的回答,然后再回来阅读这个问题。 - The Vivandiere
6
这只是又一个例子,说明从SomeBase继承在大多数情况下就像硬编码的方式来组合类型为SomeBase的匿名成员。和其他成员一样,它有一个访问修饰符,对外部访问施加了相同的控制。 - underscore_d
2
@ZheChen 如果我有类Person的对象Tom和Jerry,其中包含私有字段age,如何使用Tom访问(并修改?)Jerry的年龄? - gen
2
你能解释一下你所说的“了解‘继承’”是什么意思吗?我可以理解“我可以访问这个,但我无法访问那个”,但当有人说“我知道A从B继承而来”时,我就不明白了,我在这里做什么,我是在检查继承关系吗? - lineil
显示剩余9条评论

128

限制继承的可见性将导致代码无法看到某些类继承了另一个类:从派生类到基类的隐式转换将不起作用,从基类到派生类的static_cast也不起作用。

只有类的成员/友元可以看到私有继承,只有类的成员/友元和派生类可以看到保护继承。

公共继承

  1. IS-A继承。按钮是窗口,任何需要窗口的地方都可以传递按钮。

class button : public window { };

protected继承

  1. 以保护方式实现继承。极少使用。在 boost::compressed_pair 中用于从空类派生并利用空基类优化节省内存(下面的示例不使用模板以保持简洁):

struct empty_pair_impl : protected empty_class_1 
{ non_empty_class_2 second; };

struct pair : private empty_pair_impl {
  non_empty_class_2 &second() {
    return this->second;
  }

  empty_class_1 &first() {
    return *this; // notice we return *this!
  }
};

私有继承

  1. 实现为术语。基类的使用仅用于实现派生类。对trait非常有用,如果大小很重要(仅包含函数的空trait将利用空基类优化)。但通常情况下,包含更好的解决方案。对于字符串,大小很关键,因此常常在这里看到它的使用。

template<typename StorageModel>
struct string : private StorageModel {
public:
  void realloc() {
    // uses inherited function
    StorageModel::realloc();
  }
};

公共成员

  1. Aggregate 聚合

class pair {
public:
  First first;
  Second second;
};
  • 访问器

  • class window {
    public:
        int getWidth() const;
    };
    

    受保护的成员

    1. 为派生类提供增强的访问权限

    class stack {
    protected:
      vector<element> c;
    };
    
    class window {
    protected:
      void registerClass(window_descriptor w);
    };
    

    私有成员

    1. 保留实现细节

    class window {
    private:
      int width;
    };
    
    请注意,C风格的强制类型转换允许以定义和安全的方式将派生类转换为受保护或私有基类,并且也可以将其反向转换。应该尽量避免这样做,因为它可能会使代码依赖于实现细节,但如果必要,您可以利用这种技术。

    7
    我认为Scott Myers(尽管我很喜欢他的作品)对普遍混淆有很多责任。我现在认为他关于IS-A和IS-IMPLEMENTED-IN-TERMS-OF的类比不足以解释正在发生的事情。 - DangerMouse

    92

    这三个关键字在完全不同的上下文中也用于指定可见性继承模型

    该表收集了组件声明和继承模型的所有可能组合,呈现了子类在完全定义时对组件的访问权限。

    enter image description here

    上面的表格可以按如下方式解释(看一下第一行):

     

    如果一个组件被声明为公共的,并且它的类被继承为公共的,则结果访问将是公共的。

    例如:

     class Super {
        public:      int p;
        private:     int q;
        protected:   int r;
     };
    
     class Sub : private Super {};
    
     class Subsub : public Sub {};
    

    在类 Subsub 中,变量 pqr 的访问权限为 none

    另一个例子:

    class Super {
        private:     int x;
        protected:   int y;
        public:      int z;
     };
    class Sub : protected Super {};
    

    在类 Sub 中,变量 yz 的访问权限为 protected,而变量 x 的访问权限为 none

    一个更详细的例子:

    class Super {
    private:
        int storage;
    public:
        void put(int val) { storage = val;  }
        int  get(void)    { return storage; }
    };
    int main(void) {
        Super object;
    
        object.put(100);
        object.put(object.get());
        cout << object.get() << endl;
        return 0;
    }
    

    现在让我们定义一个子类:

    class Sub : Super { };
    
    int main(void) {
        Sub object;
    
        object.put(100);
        object.put(object.get());
        cout << object.get() << endl;
        return 0;
    }
    

    定义了一个名为Sub的类,它是名为Super的类的子类,或者说Sub类派生自Super类。Sub类既不引入新变量也不引入新函数。那么这是否意味着Sub类的任何对象都继承了Super类的所有特征,实际上是Super类对象的副本?

    不是这样的。

    如果编译以下代码,我们将只会得到编译错误,报告putget方法不可访问。为什么?

    当省略可见性修饰符时,编译器假定我们要应用所谓的私有继承。这意味着所有公共超类组件都变成了私有访问,私有超类组件将根本无法访问。因此,您不能在子类中使用后者。

    我们必须告诉编译器要保留先前使用的访问策略。

    class Sub : public Super { };
    

    不要被误导:这并不意味着超类的私有成员(如存储变量)会以某种神奇的方式变成公共成员。私有组件仍将保持为私有的,公共组件也将保持为公共的。

    Sub 类的对象可以做与其由Super类创建的旧兄弟几乎相同的事情。“几乎”是因为作为子类的事实也意味着该类失去了对超类私有成员的访问权。我们不能编写一个Sub类成员函数,它能直接操作存储变量。

    这是一个非常严重的限制。是否有任何解决方法?

    是的。

    第三个访问级别称为受保护的。关键字protected意味着使用它标记的组件在任何子类中使用时都像公共组件一样,而对于世界上的其他部分则看起来像私有组件。-- 这仅适用于公开继承的类(例如我们示例中的Super类)--

    class Super {
    protected:
        int storage;
    public:
        void put(int val) { storage = val;  }
        int  get(void)    { return storage; }
    };
    
    class Sub : public Super {
    public:
        void print(void) {cout << "storage = " << storage;}
    };
    
    int main(void) {
        Sub object;
    
        object.put(100);
        object.put(object.get() + 1);
        object.print();
        return 0;
    }
    

    正如您在示例代码中看到的那样,我们为Sub类添加了新功能,它执行了一个重要的操作:访问了来自Super类的存储变量。

    如果该变量被声明为私有,则这是不可能的。在主函数作用域中,该变量仍然保持隐藏,因此如果您编写以下任何内容:

    object.storage = 0;
    

    编译器将告知您出现了错误:'int Super::storage' 是受保护的

    最后,该程序将产生以下输出:

    storage = 101
    

    4
    第一个提到缺少修饰符(比如 Class : SuperClass)的人将得到 private。这是其他人遗漏的重要细节,需要详细解释。+1 - Water
    2
    在我看来有点过度,但我喜欢开头的表格。 - cp.engr
    公有继承中的受保护成员在派生类中变为私有成员,对吗?表格上说它变成了受保护的? - KcFnMi
    @KcFnMi 不,它的意思是:如果一个组件被声明为 protected,并且它的类被继承为 public,则结果访问级别为 protected。 - BugShotGG
    上面的图表对我来说已经足够理解这个概念了。谢谢 :) - herr_azad

    70

    这与派生类如何公开基类的公共成员有关。

    • public -> 基类的公共成员将保持为公共 (通常是默认值)
    • protected -> 基类的公共成员将被保护
    • private -> 基类的公共成员将变为私有

    正如litb所指出的,公共继承是大多数编程语言中常见的传统继承方式。它模拟了一个“IS-A”关系。私有继承,作为C ++中独特的一部分,“IMPLEMENTED IN TERMS OF”的关系。也就是说,您想在派生类中使用公共接口,但不希望派生类的用户可以访问该接口。许多人认为,在这种情况下,您应该聚合基类,即将基类作为派生类的成员,以便重用基类的功能。


    13
    更好的说法是:“public:继承将被所有人看到。”"protected:继承将只被派生类和友元所看到。""private:继承将只被该类本身和友元所看到。" 这与您的措辞不同,因为不仅成员可以隐形,而且IS-A关系也可能是隐形的。 - Johannes Schaub - litb
    4
    我曾经使用私有继承,就像Doug T所描述的那样,即“你想在派生类中使用公共接口,但不希望派生类的用户访问该接口”。我基本上是用它来封闭旧接口,并通过派生类公开另一个接口。 - Rich

    38
    Member in base class : Private   Protected   Public   
    

    继承类型:            对象继承为:

    Private            :   Inaccessible   Private     Private   
    Protected          :   Inaccessible   Protected   Protected  
    Public             :   Inaccessible   Protected   Public
    

    23
    这是误导性的。基类的私有成员与普通的私有类成员行为完全不同-它们根本无法从派生类中访问。我认为你对三个“Private”的列应该改为“无法访问”的列。请参见Kirill V. Lyadvinsky对这个问题的答案。 - Sam Kauffman

    29

    1) 公有继承:

    a. 基类的私有成员在派生类中不可访问。

    b. 基类的保护成员在派生类中仍然是保护的。

    c. 基类的公有成员在派生类中仍然是公有的。

    因此,其他类可以通过派生类对象使用基类的公有成员。

    2) 保护继承:

    a. 基类的私有成员在派生类中不可访问。

    b. 基类的保护成员在派生类中仍然是保护的。

    c. 基类的公有成员也成为派生类的保护成员。

    因此,其他类不能通过派生类对象使用基类的公有成员;但它们对于派生类的子类是可用的。

    3) 私有继承:

    a. 基类的私有成员在派生类中不可访问。

    b. 基类的保护和公有成员成为派生类的私有成员。

    因此,其他类无法通过派生类对象访问基类的任何成员,因为它们在派生类中是私有的。因此,即使是派生类的子类也无法访问它们。


    20

    公有继承模拟了一种IS-A(是一个)关系。使用

    class B {};
    class D : public B {};
    
    每一个 D 都是一个 B
    私有继承建立了一个IS-IMPLEMENTED-USING关系(或者称为其他什么)。带着。
    class B {};
    class D : private B {};
    

    一个 D 不是一个 B,但是每一个 D 在它的实现中都使用了它的 B。私有继承可以通过使用包含来代替而总是被消除:

    class B {};
    class D {
      private: 
        B b_;
    };
    

    对于这个D,也可以使用B进行实现,这种情况下需要使用它的b_。与继承相比,包含关系在类型之间的耦合性较小,因此通常应该被优先考虑。有时候使用包含而不是私有继承并不像私有继承那么方便。经常这只是因为懒得找借口。

    我认为没有人知道protected继承模型的作用。至少我还没有看到什么令人信服的解释。


    有人说一个作为关系。就像用椅子当锤子一样。这里椅子:受保护的锤子。 - user4951
    当使用包含而不是私有继承时,不如私有继承方便?您能举个例子来解释一下吗? - Destructor
    1
    @Pravasi:如果D私有地派生自B,它可以覆盖B的虚函数。(例如,如果B是观察者接口,则D可以实现它并将this传递给需要这样一个接口的函数,而不是每个人都能使用D作为观察者。)此外,D可以通过执行using B::member来选择性地在其接口中使B的成员可用。当B是成员时,这两种情况在语法上都很不方便实现。 - sbi
    @sbi:虽然旧,但是在CRTP和/或虚拟情况下,包含是不可行的(正如您在评论中正确描述的那样-但这意味着如果B具有抽象方法并且您不允许触摸它,则无法将其建模为包含)。我发现使用protected继承和protected构造函数很有用,其中包含一个virtual基类:struct CommonStuff { CommonStuff(Stuff*) {/* assert !=0 */ } }; struct HandlerMixin1 : protected virtual CommonStuff { protected: HandlerMixin1() : CommonStuff(nullptr) {} /*...*/ }; struct Handler : HandlerMixin1, ... { Handler(Stuff& stuff) : CommonStuff(&stuff) {} }; - lorro

    11
    Accessors    | Base Class | Derived Class | World
    —————————————+————————————+———————————————+———————
    public       |      y     |       y       |   y
    —————————————+————————————+———————————————+———————
    protected    |      y     |       y       |   n
    —————————————+————————————+———————————————+———————
    private      |            |               |    
      or         |      y     |       n       |   n
    no accessor  |            |               |
    
    y: accessible
    n: not accessible
    

    基于 这个 Java 示例,我认为一个小表格胜过千言万语 :)


    Java只有公共继承。 - Zelldon
    这不是讨论Java的话题,但不,你错了...请参考我上面回答中的链接获取详细信息。 - Enissay
    1
    你提到了Java,所以这就是话题。而且你的例子处理了在Java中使用的修饰符。问题是关于继承中不存在的修饰符造成的差异。如果超类中的字段是公共的,而继承是私有的,则该字段仅在子类内部可访问。在外部,没有迹象表明子类扩展了超类。但是你的表格只解释了字段和方法的修饰符。 - Zelldon

    10

    如果你从另一个类公开继承,那么每个人都知道你正在继承,并且可以通过基类指针被任何人使用多态。

    如果你只保护地继承,那么只有你的子类才能使用你的多态性。

    如果你私有地继承,那么只有你自己才能执行父类方法。

    这基本上象征着其他类对于你和父类的关系所了解的知识。


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