访问说明符
在 class/struct 或 union 的 成员说明 中定义其后继成员的可访问性。
在派生类声明的 基类说明符 中定义继承自其后继的基类的成员的可访问性。
语法
public : 成员说明
|
(1) | ||||||||
protected : 成员说明
|
(2) | ||||||||
private : 成员说明
|
(3) | ||||||||
public 基类 | (4) | ||||||||
protected 基类 | (5) | ||||||||
private 基类 | (6) | ||||||||
解释
每个类成员(静态、非静态、函数、类型等)的名字都具有与其关联的“成员访问”。在程序的任何位置使用成员的名字时都会检查其访问,而且如果它不满足访问规则,那么程序不能编译:
#include <iostream> class Example { public: // 此点后的所有声明都是公开的 void add(int x) { // 成员 "add" 具有公开访问 n += x; // OK:从 Example::add 可以访问私有的 Example::n } private: // 此点后的所有声明都是私有的 int n = 0; // 成员 "n" 具有私有访问 }; int main() { Example e; e.add(1); // OK:从 main 可以访问公开的 Example::add // e.n = 7; // 错误:从 main 不能访问私有的 Example::n }
访问说明符给予类作者决定哪些类成员能被类的用户所访问(即类的接口),而哪些成员用于内部使用(即其实现)的能力。
细节
类的所有成员(成员函数体,成员对象的初始化器,以及整个嵌套类定义),都拥有对类所能访问的所有名字的访问权。成员函数内的局部类拥有对成员函数所能访问的所有名字的访问权。
以关键词 class
定义的类,其成员和其基类默认具有私有访问。以关键词 struct
定义的类,其成员和其基类默认具有公开访问。union 的成员默认具有公开访问。
如果要向其他函数或类授予对受保护或私有成员的访问权,可以使用友元声明。
可访问性应用到所有名字,而不考虑其来源,因此受检查的是以 typedef 或 using 声明引入的名字,而非其所指涉的名字:
class A : X { class B { }; // 在 A 中,B 是私有的 public: typedef B BB; // 在 A 中,BB 是公开的 }; void f() { A::B y; // 错误:A::B 是私有的 A::BB x; // OK:A::BB 是公开的 }
成员访问不影响可见性:私有和私有继承的成员对重载决议可见并被其考虑,到不可访问基类的隐式转换也仍被考虑,等等。成员访问检查是对任何给定语言构造进行解释之后的最后一步。此规则的目的是使得以 public
替换任何 private
时始终不会改变程序的行为。
对于默认函数实参中以及默认模板形参中所使用的名字的访问检查,在声明点而非在使用点进行。
对虚函数的名字的访问规则,在调用点,使用(用于代表调用该成员函数的对象的)表达式的类型进行检查。忽略最终覆盖函数的访问:
struct B { virtual int f(); }; // 在 B 中,f 是公开的 class D : public B { private: int f(); }; // 在 D 中,f 是私有的 void f() { D d; B& b = d; b.f(); // OK:B::f() 是公开的,调用 D::f(),即使它是私有的 d.f(); // 错误:D::f() 是私有的 }
根据无限定名字查找为私有的名字,有可能通过有限定名字查找访问:
class A {}; class B : private A { }; class C : public B { A* p; // 错误:无限定名字查找找到作为 B 的私有基类的 A ::A* q; // OK:有限定名字查找找到命名空间层级的声明 };
如果一个名字可以通过继承图中多条路径访问,那么它的访问是所有路径中带最大访问的路径的访问:
class W { public: void f(); }; class A : private virtual W { }; class B : public virtual W { }; class C : public A, public B { void f() { W::f(); } // OK: C 可以通过 B 访问 W };
类中可以以任意顺序出现任意数量的访问说明符。成员访问说明符可能影响类的布局:非静态数据成员的地址只保证对于未被访问说明符分隔 (C++11 前)具有相同访问 (C++11 起)的成员以声明顺序增加。
对于标准布局类型 (StandardLayoutType) ,所有非静态数据成员必须具有相同访问。 |
(C++11 起) |
在类中重声明成员时,成员访问必须相同:
struct S { class A; // S::A 公开 private: class A {}; // 错误:不能更改访问 };
公开成员访问
公开成员组成类公开接口的一部分(公开接口的其他部分是由 ADL 所找到的非成员函数)。
类的公开成员可以在任意位置访问。
class S { public: // n、E、A、B、C、U、f 是公开成员 int n; enum E {A, B, C}; struct U {}; static void f() {} }; int main() { S::f(); // S::f 可以在 main 访问 S s; s.n = S::B; // S::n 与 S::B 可以在 main 访问 S::U x; // S::U 可以在 main 访问 }
受保护成员访问
受保护成员组成所在类针对其派生类的接口(与类的公开接口有别)。
类的受保护成员只能为下列者所访问
struct Base { protected: int i; private: void g(Base& b, struct Derived& d); }; struct Derived : Base { void f(Base& b, Derived& d) // 派生类的成员函数 { ++d.i; // OK:d 的类型是 Derived ++i; // OK:隐含的 '*this' 的类型是 Derived // ++b.i; // 错误:不能通过 Base 访问受保护成员 // (否则可能更改另一派生类,假设为 Derived2 的基实现) } }; void Base::g(Base& b, Derived& d) // Base 的成员函数 { ++i; // OK ++b.i; // OK ++d.i; // OK } void x(Base& b, Derived& d) // 非成员非友元 { // ++b.i; // 错误:非成员不能访问 // ++d.i; // 错误:非成员不能访问 }
组成指向受保护成员的指针时,必须在其声明中使用派生类:
struct Base { protected: int i; }; struct Derived : Base { void f() { // int Base::* ptr = &Base::i; // 错误:必须使用 Derived 来指名 int Base::* ptr = &Derived::i; // OK } };
私有成员访问
私有成员组成类的实现,以及针对类的其他成员的私有接口。
类的私有成员仅对类的成员和友元可访问,无关乎成员在相同还是不同实例:
class S { private: int n; // S::n 私有 public: S() : n(10) {} // this->n 可以在 S::S 访问 S(const S& other) : n(other.n) {} // other.n 可以在 S::S 访问 };
显式转型(C 风格与函数风格)允许从派生类左值转型为到其私有基类的引用,或从指向派生类的指针转型为指向其私有基类的指针。
继承
公开、受保护和私有继承的含义,见派生类。