C++继承与访问说明符详解:public、private和protected

在前面的课程中,你已初步了解了基类继承的工作机制。迄今为止,我们所有示例都采用公有继承(public inheritance),即派生类以公有方式继承基类。

本文将深入探讨公有继承,并介绍另外两种继承方式——私有继承(private inheritance)和受保护继承(protected inheritance)。同时,我们也将研究不同类型的继承与各类访问说明符之间的相互作用,从而决定成员的可访问性。

截至目前,你已见过 private 与 public 两种访问说明符,它们决定了类成员可被谁访问。简要回顾:public 成员可被任何代码访问;private 成员只能被本类成员函数或友元访问。这也意味着派生类无法直接访问基类的 private 成员!

class Base
{
private:
    int m_private {}; // 仅可由 Base 的成员及友元访问(派生类不可)
public:
    int m_public {};  // 可被任何代码访问
};

上述规则简洁明了,应当已为你所熟悉。

受保护访问说明符(protected)

当涉及继承层次时,情况会稍显复杂。

C++ 还提供了第三种访问说明符 protected,此前未予讨论,因为它只在继承语境下才有意义。protected 成员允许所属类自身、其友元以及派生类访问,但禁止类外代码直接访问。

class Base
{
public:
    int m_public {};     // 可被任何代码访问
protected:
    int m_protected {};  // 可被 Base 成员、友元及派生类访问
private:
    int m_private {};    // 仅可被 Base 成员及友元访问(派生类不可)
};

class Derived: public Base
{
public:
    Derived()
    {
        m_public = 1;    // 合法:在派生类中可访问 public 基类成员
        m_protected = 2; // 合法:在派生类中可访问 protected 基类成员
        m_private = 3;   // 非法:无法直接访问基类 private 成员
    }
};

int main()
{
    Base base;
    base.m_public = 1;    // 合法:类外可访问 public 成员
    base.m_protected = 2; // 非法:类外不可访问 protected 成员
    base.m_private = 3;   // 非法:类外不可访问 private 成员

    return 0;
}

以上示例可见,基类的 protected 成员 m_protected 可被派生类直接访问,但类外代码则不可。

何时应使用 protected 访问说明符?

若基类的某属性声明为 protected,则派生类可直接访问该属性。一旦将来你对该 protected 属性做任何修改(如类型、语义等),很可能需要同时修改基类及所有派生类。

因此,protected 访问说明符最适用于以下场景:

  • 你(或你的团队)计划从自身编写的类派生新类;
  • 派生类数量有限。

这样,当基类实现发生变更、需要同步更新派生类时,你可以亲自完成这些调整,且工作量可控(因派生类数量少)。

若将成员设为 private,则类外及派生类均无法直接修改基类实现,这有利于隔离实现细节、保证不变式成立。但代价是,你可能需要为 public 或 protected 接口提供更多函数,从而增加构建、测试与维护成本。

一般而言,能设为 private 就优先设为 private;仅当确实计划派生,且为 private 成员构建接口成本过高时,才考虑使用 protected。

最佳实践:优先使用 private 成员,而非 protected 成员。

不同的继承方式及其影响

类继承有三种方式:public、protected 与 private。只需在继承列表中显式指明即可:

// 公有继承 Base
class Pub: public Base
{
};

// 受保护继承 Base
class Pro: protected Base
{
};

// 私有继承 Base
class Pri: private Base
{
};

class Def: Base   // 未指明时默认为私有继承
{
};

若未显式指定继承方式,C++ 默认为 private 继承(与成员默认 private 访问一致)。

由此共产生 9 种组合:3 种成员访问说明符(public、private、protected)× 3 种继承方式(public、private、protected)。

区别何在?简言之,成员被继承后,其在派生类中的访问级别可能因继承方式而异;换言之,基类成员在派生类中的可见性会发生变化。

继续阅读时请牢记以下规则:

  1. 类总能访问自身的(非继承)成员;
  2. 类外代码依据所访问类的访问说明符决定能否访问;
  3. 派生类访问继承而来的成员时,取决于继承方式与基类成员的访问说明符。

公有继承(public inheritance)

公有继承是使用最广泛的继承方式,几乎在所有场景下都应首选。规则最简单:

  • 基类的 public 成员在派生类中仍为 public;
  • 基类的 protected 成员在派生类中仍为 protected;
  • 基类的 private 成员在派生类中仍不可访问。
基类访问说明符公有继承后的访问级别
publicpublic
protectedprotected
private不可访问

示例:

class Base
{
public:
    int m_public {};
protected:
    int m_protected {};
private:
    int m_private {};
};

class Pub: public Base   // 注意:公有继承
{
    // 公有继承意味着:
    // 继承的 public 成员仍为 public(m_public 视为 public)
    // 继承的 protected 成员仍为 protected(m_protected 视为 protected)
    // 继承的 private 成员仍不可访问(m_private 不可访问)
public:
    Pub()
    {
        m_public = 1;    // 合法:m_public 在 Pub 中为 public
        m_protected = 2; // 合法:m_protected 在 Pub 中为 protected
        m_private = 3;   // 非法:派生类无法访问基类 private 成员
    }
};

int main()
{
    // 类外访问依据被访问类的访问说明符
    Base base;
    base.m_public = 1;    // 合法:m_public 在 Base 中为 public
    base.m_protected = 2; // 非法:m_protected 在 Base 中为 protected
    base.m_private = 3;   // 非法:m_private 在 Base 中为 private

    Pub pub;
    pub.m_public = 1;    // 合法:m_public 在 Pub 中为 public
    pub.m_protected = 2; // 非法:m_protected 在 Pub 中为 protected
    pub.m_private = 3;   // 非法:m_private 在 Pub 中不可访问

    return 0;
}

该示例与之前引入 protected 的示例一致,只是额外实例化了派生类,以证明公有继承时派生类与基类表现一致。

除非有明确理由,否则应始终使用公有继承。

最佳实践:如无特殊需求,请使用公有继承。

受保护继承(protected inheritance)

受保护继承是最罕见的继承方式,几乎不使用。规则如下:

  • 基类的 public 与 protected 成员在派生类中均变为 protected;
  • 基类的 private 成员仍不可访问。
基类访问说明符受保护继承后的访问级别
publicprotected
protectedprotected
private不可访问

鉴于其罕见性,此处略去示例,仅以上表总结。

私有继承(private inheritance)

采用私有继承时,基类所有成员在派生类中均变为 private:

  • public 与 protected 成员变为 private;
  • private 成员仍不可访问。

注意:这并不影响派生类自身访问继承成员,而是影响通过派生类对象在外部访问这些成员。

class Base
{
public:
    int m_public {};
protected:
    int m_protected {};
private:
    int m_private {};
};

class Pri: private Base   // 注意:私有继承
{
    // 私有继承意味着:
    // 继承的 public 成员变为 private(m_public 视为 private)
    // 继承的 protected 成员变为 private(m_protected 视为 private)
    // 继承的 private 成员仍不可访问(m_private 不可访问)
public:
    Pri()
    {
        m_public = 1;    // 合法:m_public 在 Pri 中为 private
        m_protected = 2; // 合法:m_protected 在 Pri 中为 private
        m_private = 3;   // 非法:派生类无法访问基类 private 成员
    }
};

int main()
{
    // 类外访问依据被访问类的访问说明符
    Base base;
    base.m_public = 1;    // 合法:m_public 在 Base 中为 public
    base.m_protected = 2; // 非法:m_protected 在 Base 中为 protected
    base.m_private = 3;   // 非法:m_private 在 Base 中为 private

    Pri pri;
    pri.m_public = 1;    // 非法:m_public 在 Pri 中为 private
    pri.m_protected = 2; // 非法:m_protected 在 Pri 中为 private
    pri.m_private = 3;   // 非法:m_private 在 Pri 中不可访问

    return 0;
}

总结如下表:

基类访问说明符私有继承后的访问级别
publicprivate
protectedprivate
private不可访问

当派生类与基类之间没有明显的"is-a"关系,但派生类内部需要复用基类实现时,私有继承可能有用。此时,我们不希望基类的 public 接口通过派生类对象暴露给外部。

实际开发中,私有继承亦很少使用。

综合示例

class Base
{
public:
    int m_public {};
protected:
    int m_protected {};
private:
    int m_private {};
};
  • Base 自身可无限制访问所有成员。
  • 类外代码仅能访问 m_public
  • 派生类可访问 m_publicm_protected
class D2 : private Base   // 注意:私有继承
{
    // 私有继承意味着:
    // 继承的 public 成员变为 private
    // 继承的 protected 成员变为 private
    // 继承的 private 成员仍不可访问
public:
    int m_public2 {};
protected:
    int m_protected2 {};
private:
    int m_private2 {};
};
  • D2 自身可无限制访问所有成员。
  • D2 可访问 Base 的 m_publicm_protected,不能访问 m_private
  • 因 D2 私有继承 Base,m_publicm_protected 在 D2 中视为 private,故类外代码或 D2 的派生类均无法通过 D2 对象访问这些变量。
class D3 : public D2
{
    // 公有继承意味着:
    // 继承的 public 成员仍为 public
    // 继承的 protected 成员仍为 protected
    // 继承的 private 成员仍不可访问
public:
    int m_public3 {};
protected:
    int m_protected3 {};
private:
    int m_private3 {};
};
  • D3 自身可无限制访问所有成员。
  • D3 可访问 D2 的 m_public2m_protected2,不能访问 m_private2
  • 因 D3 公有继承 D2,m_public2m_protected2 保持原访问级别。
  • D3 无法访问 Base 的 m_private(已在 Base 中设为 private),也无法访问 Base 的 m_protectedm_public,因为它们在 D2 私有继承时已变为 private。

总结

访问说明符、继承方式与派生类之间的交互常令人困惑。为尽量厘清:

  1. 类(及其友元)总能访问自身非继承成员;访问说明符仅影响外部代码及派生类。
  2. 派生类继承成员后,这些成员在派生类中的访问级别可能变化;这并不影响派生类自身非继承成员,而仅影响外部代码以及"派生类的派生类"对这些继承成员的访问。

全部组合一览表:

基类访问说明符公有继承后的访问级别私有继承后的访问级别受保护继承后的访问级别
publicpublicprivateprotected
protectedprotectedprivateprotected
private不可访问不可访问不可访问

最后,尽管上例仅展示了成员变量,上述访问规则对所有成员(成员函数、类内声明的类型等)均适用。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"