class/structunion 的 成员说明 中,定义其后继成员的可访问性。

派生类声明的 基类说明符 中,定义继承自其后继的基类的成员的可访问性。

语法

public : 成员说明 (1)
protected : 成员说明 (2)
private : 成员说明 (3)
public 基类 (4)
protected 基类 (5)
private 基类 (6)

1) 该访问说明符之后的各个成员具有公开成员访问

2) 该访问说明符之后的各个成员具有受保护成员访问

3) 该访问说明符之后的各个成员具有私有成员访问

4) 公开继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中保持其成员访问,而基类的私有成员对派生类不可访问

5) 受保护继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中是受保护成员,而基类的私有成员对派生类不可访问

6) 私有继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中是私有成员,而基类的私有成员对派生类不可访问

解释

每个成员(静态、非静态、函数、类型等)的名字都具有与其关联的“成员访问”。在程序的任何位置使用成员的名字时,检查其访问,而且若它不满足访问规则,则程序不能编译:

运行此代码

  1. #include <iostream>
  2. class Example {
  3. public: // 此点后的所有声明为公开
  4. void add(int x) { // 成员 "add" 具有公开访问
  5. n += x; // OK:从 Example::add 可以访问受保护的 Example::n
  6. }
  7. private: // 此点后的所有声明为私有
  8. int n = 0; // 成员 "n" 具有私有访问
  9. };
  10. int main()
  11. {
  12. Example e;
  13. e.add(1); // OK:从 main 可以访问公开的 Example::add
  14. // e.n = 7; // 错误:从 main 不能访问私有的 Example::n
  15. }

访问说明符给予类作者决定哪些类成员能被类的用户所访问(即类的接口),而哪些成员用于内部使用(即其实现)的能力。

细节

类的所有成员(成员函数体,成员对象的初始化器,以及整个嵌套类定义),都拥有对类所能访问的所有名字的访问权。成员函数内的局部类拥有对成员函数所能访问的所有名字的访问权。

以关键词 class 定义的类,其成员和其基类默认具有私有访问。以关键词 struct 定义的类,其成员和其基类默认具有公开访问。union 的成员默认具有公开访问。

若要向其他函数或类授予对受保护或私有成员的访问权,可使用友元声明

可访问性应用到所有名字,而不考虑其来源,因此受检查的是以 typedefusing 声明引入的名字,而非其所指涉的名字:

  1. class A : X {
  2. class B { }; // B 在 A 中为私有
  3. public:
  4. typedef B BB; // BB 为公开
  5. };
  6. void f() {
  7. A::B y; // 错误:A::B 为私有
  8. A::BB x; // OK:A::BB 为公开
  9. }

成员访问不影响可见性:私有和私有继承的成员对重载决议可见并被其考虑,到不可访问基类的隐式转换也仍被考虑,等等。成员访问检查是对任何给定语言构造进行解释之后的最后一步。此规则的目的是使得以 public 替换任何 private 时始终不会改变程序的行为。

对于默认函数实参中以及默认模板形参中所使用的名字的访问检查,在声明点而非在使用点进行。

虚函数的名字的访问规则,在调用点,使用(用于代表调用该成员函数的对象的)表达式的类型进行检查。忽略最终覆盖函数的访问:

  1. struct B { virtual int f(); }; // f 在 B 中为公开
  2. class D : public B { private: int f(); }; // f 在 D 中为私有
  3. void f() {
  4. D d;
  5. B& b = d;
  6. b.f(); // OK:B::f() 为公开,调用 D::f(),即使它为私有
  7. d.f(); // 错误:D::f() 为私有
  8. }

根据无限定名字查找为私有的名字,有可能通过有限定名字查找访问:

  1. class A {};
  2. class B : private A { };
  3. class C : public B {
  4. A* p; // 错误:无限定名字查找找到作为 B 的私有基类的 A
  5. ::A* q; // OK:有限定名字查找找到命名空间层级的声明
  6. };

可通过继承图中多条路径访问的名字,具有带最大访问的路径上的访问:

  1. class W { public: void f(); };
  2. class A : private virtual W { };
  3. class B : public virtual W { };
  4. class C : public A, public B {
  5. void f() { W::f(); } // OK:W 通过 B 对 C 是可访问的
  6. };

类中可以任何顺序出现任何数量的访问说明符。成员访问说明符可能影响类的布局:非静态数据成员的地址只保证对于具有相同访问的成员以声明顺序增加。对于标准布局类型 (StandardLayoutType) ,所有非静态数据成员必须具有相同访问。

在类中重声明成员时,必须在相同成员访问下进行:

  1. struct S {
  2. class A; // S::A 公开
  3. private:
  4. class A {}; // 错误:不能更改访问
  5. };

公开成员访问

公开成员组成类公开接口的一部分(公开接口的其他部分是由 ADL 所找到的非成员函数)。

类的公开成员可在任何位置访问。

  1. class S {
  2. public: // n、E、A、B、C、U、f 是公开成员
  3. int n;
  4. enum E {A, B, C};
  5. struct U {};
  6. static void f() {}
  7. };
  8. int main()
  9. {
  10. S::f(); // S::f 于 main 可访问
  11. S s;
  12. s.n = S::B; // S::n 与 S::B 于 main 可访问
  13. S::U x; // S::U 于 main 可访问
  14. }

受保护成员访问

受保护成员访问组成针对类对其派生类的接口(与类的公开接口有别)。

类的受保护成员只能为下列者所访问

1) 该类的成员和友元

2) 派生自该类的任何类的成员和友元 (C++17 前),但仅在访问受保护成员所通过的对象的类是该派生类或该派生类的派生类时允许:

  1. struct Base {
  2. protected:
  3. int i;
  4. private:
  5. void g(Base& b, struct Derived& d);
  6. };
  7.  
  8. struct Derived : Base {
  9. void f(Base& b, Derived& d) // 派生类的成员函数
  10. {
  11. ++d.i; // OK:d 的类型是 Derived
  12. ++i; // OK:隐含的 '*this' 的类型为 Derived
  13. // ++b.i; // 错误:不能通过 Base 访问受保护成员
  14. // (否则可能更改另一派生类,假设为 Derived2 的基实现)
  15. }
  16. };
  17.  
  18. void Base::g(Base& b, Derived& d) // Base 的成员函数
  19. {
  20. ++i; // OK
  21. ++b.i; // OK
  22. ++d.i; // OK
  23. }
  24.  
  25. void x(Base& b, Derived& d) // 非成员非友元
  26. {
  27. // ++b.i; // 错误:非成员不能访问
  28. // ++d.i; // 错误:非成员不能访问
  29. }

组成指向受保护成员的指针时,必须在其声明中使用派生类:

  1. struct Base {
  2. protected:
  3. int i;
  4. };
  5.  
  6. struct Derived : Base {
  7. void f()
  8. {
  9. // int Base::* ptr = &Base::i; // 错误:必须使用 Derived 来指名
  10. int Base::* ptr = &Derived::i; // OK
  11. }
  12. };

私有成员访问

私有成员组成类的实现,以及针对类的其他成员的私有接口。

类的私有成员仅对类的成员和友元可访问,无关乎成员在相同还是不同实例:

  1. class S {
  2. private:
  3. int n; // S::n 私有
  4. public:
  5. S() : n(10) {} // this->n 可于 S::S 访问
  6. S(const S& other) : n(other.n) {} // other.n 可于 S::S 访问
  7. };

显式转型(C 风格与函数风格)允许从派生类左值转型为到其私有基类的引用,或从指向派生类的指针转型为指向其私有基类的指针。

继承

公开、受保护和私有继承的含义,见派生类