24-5 继承与访问限定符

在本章之前的课程中,你已经初步了解了基类继承的工作原理。迄今为止的所有示例中,我们都采用了公共继承——即派生类公开继承基类。

本节课我们将深入探讨公共继承,同时介绍另外两种继承类型(私有继承和受保护继承)。同时我们将探讨不同继承类型如何与访问限定符交互作用,从而允许或限制成员访问权限。

至此你已了解私有和公有访问限定符,它们决定了谁能访问类成员。简单回顾:公有成员可被任何对象访问;私有成员仅限同类成员函数或友元访问。这意味着派生类无法直接访问基类的私有成员!

class Base
{
private:
    int m_private {}; // can only be accessed by Base members and friends (not derived classes)
public:
    int m_public {}; // can be accessed by anybody
};

这相当简单明了,你现在应该已经相当熟悉了。


受保护访问限定符

在处理继承类时,情况会变得稍复杂一些。

C++ 还有第三种访问限定符尚未提及,因为它仅在继承上下文中有用。受保护protected访问限定符允许成员所属的类、友元类和派生类访问该成员。但受保护成员无法从类外部访问。

class Base
{
public:
    int m_public {}; // can be accessed by anybody
protected:
    int m_protected {}; // can be accessed by Base members, friends, and derived classes
private:
    int m_private {}; // can only be accessed by Base members and friends (but not derived classes)
};

class Derived: public Base
{
public:
    Derived()
    {
        m_public = 1; // allowed: can access public base members from derived class
        m_protected = 2; // allowed: can access protected base members from derived class
        m_private = 3; // not allowed: can not access private base members from derived class
    }
};

int main()
{
    Base base;
    base.m_public = 1; // allowed: can access public members from outside class
    base.m_protected = 2; // not allowed: can not access protected members from outside class
    base.m_private = 3; // not allowed: can not access private members from outside class

    return 0;
}

image
image

在上例中,您可以看到受保护的基类成员 m_protected 可被派生类直接访问,但不能被公共类访问。


那么何时应该使用受保护访问限定符?

当基类中使用受保护属性时,派生类可以直接访问该成员。这意味着若后续修改该受保护属性的任何内容(类型、值的含义等),通常需要同时修改基类和所有派生类。

因此,当您(或团队)计划基于自身类进行派生,且派生类数量可控时,使用受保护访问限定符最为有效。这样,若基类实现发生变更导致派生类需更新,您可自行完成修改(且因派生类数量有限,无需耗费过多时间)。

将成员设为私有意味着公共类和派生类无法直接修改基类。这有助于隔离公共类或派生类免受实现变更影响,并确保不变量得到妥善维护。但这也意味着您的类可能需要更大的公共(或受保护)接口来支持公共类或派生类所需的所有功能,而构建、测试和维护这些接口本身会带来额外成本。

通常情况下,若条件允许应优先采用私有成员,仅当计划存在派生类且构建维护私有成员接口的成本过高时才使用受保护成员。

最佳实践:
优先选择私有成员而非受保护成员。


不同类型的继承及其对访问权限的影响

首先,类从其他类继承的方式有三种:public(公开)、protected(受保护)和 private(私有)。

实现时只需在选择要继承的类时指定所需的访问类型即可。

// Inherit from Base publicly
class Pub: public Base
{
};

// Inherit from Base protectedly
class Pro: protected Base
{
};

// Inherit from Base privately
class Pri: private Base
{
};

class Def: Base // Defaults to private inheritance
{
};

若未指定继承类型,C++ 默认采用私有继承(如同未特别声明时成员默认私有访问权限)。

由此产生 9 种组合:3 种成员访问限定符(public、private 和 protected),以及 3 种继承类型(public、private 和 protected)。

那么它们有何区别?简而言之,当成员被继承时,继承成员的访问限定符可能根据所用继承类型发生变化(仅限于派生类)。换言之,基类中为public或protected的成员,在派生类中可能改变访问限定符。

这看似有些复杂,但其实并不难理解。本节剩余部分将对此进行详细探讨。

在分析示例时请牢记以下规则:

  • 类始终可以访问自身(非继承)成员
  • public访问类成员时,依据被访问类的访问限定符
  • 派生类访问继承成员时,依据从基类继承的访问限定符。该限定符会因访问限定符类型和继承方式的不同而变化

公开继承

公开继承是迄今为止最常用的继承类型。事实上,其他继承类型极少出现或被使用,因此你应重点理解本节内容。值得庆幸的是,公开继承也是最易理解的。当你公开继承基类时,继承的公共成员保持公共属性,继承的受保护成员保持受保护属性。而继承的私有成员——因在基类中属于私有而不可访问——仍保持不可访问状态。

Access specifier in base class Access specifier when inherited publicly
Public Public
Protected Protected
Private Inaccessible

以下是一个展示其运作方式的示例:

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

class Pub: public Base // note: public inheritance
{
    // Public inheritance means:
    // Public inherited members stay public (so m_public is treated as public)
    // Protected inherited members stay protected (so m_protected is treated as protected)
    // Private inherited members stay inaccessible (so m_private is inaccessible)
public:
    Pub()
    {
        m_public = 1; // okay: m_public was inherited as public
        m_protected = 2; // okay: m_protected was inherited as protected
        m_private = 3; // not okay: m_private is inaccessible from derived class
    }
};

int main()
{
    // Outside access uses the access specifiers of the class being accessed.
    Base base;
    base.m_public = 1; // okay: m_public is public in Base
    base.m_protected = 2; // not okay: m_protected is protected in Base
    base.m_private = 3; // not okay: m_private is private in Base

    Pub pub;
    pub.m_public = 1; // okay: m_public is public in Pub
    pub.m_protected = 2; // not okay: m_protected is protected in Pub
    pub.m_private = 3; // not okay: m_private is inaccessible in Pub

    return 0;
}

这与上文介绍受保护访问限定符的示例相同,区别在于我们还实例化了派生类,旨在说明在公开继承中,基类与派生类的行为完全一致。

除非存在特殊原因,否则应优先采用公开继承。

最佳实践:
除非有特殊原因,否则应使用公开继承。


受保护继承

受保护继承是最不常见的继承方式。除极特殊情况外,它几乎从未被使用。采用受保护继承时,公共成员和受保护成员均变为受保护成员,而私有成员仍不可访问。

由于这种继承形式极为罕见,我们将跳过示例,仅通过表格进行总结:

Access specifier in base class Access specifier when inherited protectedly
Public Protected
Protected Protected
Private Inaccessible

私有继承

采用私有继承时,基类的所有成员均以私有形式继承。这意味着私有成员不可访问,受保护成员和公有成员则变为私有。

请注意,这不会影响派生类访问其父类继承成员的方式!它仅影响试图通过派生类访问这些成员的代码。

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

class Pri: private Base // note: private inheritance
{
    // Private inheritance means:
    // Public inherited members become private (so m_public is treated as private)
    // Protected inherited members become private (so m_protected is treated as private)
    // Private inherited members stay inaccessible (so m_private is inaccessible)
public:
    Pri()
    {
        m_public = 1; // okay: m_public is now private in Pri
        m_protected = 2; // okay: m_protected is now private in Pri
        m_private = 3; // not okay: derived classes can't access private members in the base class
    }
};

int main()
{
    // Outside access uses the access specifiers of the class being accessed.
    // In this case, the access specifiers of base.
    Base base;
    base.m_public = 1; // okay: m_public is public in Base
    base.m_protected = 2; // not okay: m_protected is protected in Base
    base.m_private = 3; // not okay: m_private is private in Base

    Pri pri;
    pri.m_public = 1; // not okay: m_public is now private in Pri
    pri.m_protected = 2; // not okay: m_protected is now private in Pri
    pri.m_private = 3; // not okay: m_private is inaccessible in Pri

    return 0;
}

以表格形式总结如下:

Access specifier in base class Access specifier when inherited privately
Public Private
Protected Private
Private Inaccessible

当派生类与基类没有明显关联,但内部实现中使用了基类时,私有继承会很有用。这种情况下,我们通常不希望通过派生类的对象暴露基类的公共接口(如果采用公开继承则会暴露)。

实际上,私有继承很少被使用。


最后一个例子

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

基类可以无限制地访问其自身的成员。公共类只能访问 m_public。派生类可以访问 m_public 和 m_protected。

class D2 : private Base // note: private inheritance
{
	// Private inheritance means:
	// Public inherited members become private
	// Protected inherited members become private
	// Private inherited members stay inaccessible
public:
	int m_public2 {};
protected:
	int m_protected2 {};
private:
	int m_private2 {};
};

D2可以不受限制地访问其自身的成员。D2可以访问基类Base的m_public和m_protected成员,但不能访问m_private成员。由于D2私有继承了Base,因此通过D2访问时,m_public和m_protected现在被视为私有成员。这意味着当使用D2对象时,公共成员无法访问这些变量,从D2派生的任何类同样无法访问。

class D3 : public D2
{
	// Public inheritance means:
	// Public inherited members stay public
	// Protected inherited members stay protected
	// Private inherited members stay inaccessible
public:
	int m_public3 {};
protected:
	int m_protected3 {};
private:
	int m_private3 {};
};

D3可以不受限制地访问其自身的成员。D3可以访问D2的m_public2和m_protected2成员,但无法访问m_private2。由于D3通过公开继承获得D2,因此通过D3访问时,m_public2和m_protected2仍保持其访问限定符。D3无法访问基类Base的m_private成员,该成员在Base中本就是私有成员。同样地,D3 也无法访问 Base 的 m_protected 或 m_public 成员——当 D2 继承这些成员时,它们均已变为私有属性。


摘要

访问限定符、继承类型与派生类之间的交互关系常引发诸多困惑。为尽可能厘清这些概念:

首先,类(及其友元)始终能访问自身未继承的成员。访问限定符仅影响外部对象与派生类能否访问这些成员。

其次,当派生类继承成员时,这些成员在派生类中的访问限定符可能发生改变。这不会影响派生类自身的(非继承)成员(它们拥有独立的访问限定符),仅影响外部对象及派生自该派生类的类能否访问这些继承成员。

下表列出了所有访问限定符与继承类型的组合情况:

Access specifier in base class Access specifier when inherited publicly Access specifier when inherited privately Access specifier when inherited protectedly
Public Public Private Protected
Protected Protected Private Protected
Private Inaccessible Inaccessible Inaccessible

最后需要说明的是,尽管在上面的示例中我们仅展示了使用成员变量的例子,但这些访问规则同样适用于所有成员(例如类内部声明的成员函数和类型)。

posted @ 2026-02-01 05:25  游翔  阅读(0)  评论(0)    收藏  举报