Access specifiers
在 类/结构体 或 联合体 的 成员声明序列 中,定义后续成员的可访问性。
在 派生类 声明的 基类说明符 中,定义后续基类继承成员的访问权限。
目录 |
语法
public
:
成员声明
|
(1) | ||||||||
protected
:
成员声明
|
(2) | ||||||||
private
:
成员声明
|
(3) | ||||||||
| public 基类 | (4) | ||||||||
| protected 基类 | (5) | ||||||||
| private 基类 | (6) | ||||||||
基类的私有成员始终对派生类不可访问,无论采用公有、受保护还是私有继承方式。
说明
每个 类 成员(静态、非静态、函数、类型等)的名称都具有关联的“成员访问权限”。当程序在任何地方使用成员名称时,会检查其访问权限,如果不满足访问规则,则程序无法编译:
#include <iostream> class Example { public: // 此后的所有声明均为公开 void add(int x) // 成员函数“add”具有公开访问权限 { n += x; // 正确:私有成员 Example::n 可在 Example::add 中访问 } private: // 此后的所有声明均为私有 int n = 0; // 成员“n”具有私有访问权限 }; int main() { Example e; e.add(1); // 正确:公开成员函数 Example::add 可在 main 中访问 // e.n = 7; // 错误:私有成员 Example::n 不能在 main 中访问 }
访问说明符赋予类作者决定哪些类成员可供类的使用者访问(即 接口 ),哪些成员仅用于类的内部实现(即 实现 )的能力。
详细说明
类的所有成员( 成员函数 的函数体、成员对象的初始化器,以及完整的 嵌套类定义 )都能访问该类可访问的所有名称。成员函数内的局部类可以访问该成员函数可访问的所有名称。
使用
class
关键字定义的类,其成员和基类默认具有私有访问权限。使用
struct
关键字定义的类,其成员和基类默认具有公共访问权限。
联合体
的成员默认具有公共访问权限。
为了允许其他函数或类访问受保护或私有成员,可以使用 友元声明 。
可访问性适用于所有名称,无论其来源如何,因此会检查由 typedef 或 using 声明 (继承构造函数除外)引入的名称,而非其引用的名称:
class A : X { class B {}; // B 在 A 中是私有成员 public: typedef B BB; // BB 是公开成员 }; void f() { A::B y; // 错误:A::B 是私有成员 A::BB x; // 正确:A::BB 是公开成员 }
成员访问不影响可见性:私有成员和私有继承成员的名称在重载解析时仍然可见并被考虑,到不可访问基类的隐式转换仍然会被考虑,等等。成员访问检查是在解释任何给定语言构造之后的最后一步。此规则的意图是:将任何
private
替换为
public
都不会改变程序的行为。
对 默认函数参数 以及默认 模板参数 中所用名称的访问检查是在声明点执行,而非在使用点执行。
对 虚函数 名称的访问规则在调用点进行检查,使用表示调用成员函数对象的表达式类型。最终覆盖函数的访问权限会被忽略:
struct B { virtual int f(); // f在B中为public }; class D : public B { private: int f(); // f在D中为private }; void f() { D d; B& b = d; b.f(); // 正确:B::f为public,即使D::f为private仍会调用D::f d.f(); // 错误:D::f为private }
根据非限定 名称查找 属于私有的名称,可能通过限定名称查找访问:
class A {}; class B : private A {}; class C : public B { A* p; // 错误:非限定名称查找找到的是B的私有基类A ::A* q; // 正确:限定名称查找找到的是命名空间级别的声明 };
在继承图中可通过多条路径访问的名称,具有最高访问权限路径的访问级别:
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(); // 正确:通过B类,W对C是可访问的 } };
类中可以出现任意数量的访问说明符,且顺序不限。
|
成员访问说明符可能影响 类布局 :非静态数据成员的地址仅保证在声明顺序中递增,对于 未被访问说明符分隔的成员 (C++11 前) 具有相同访问权限的成员 (C++11 起) 。 |
(直到 C++23) |
|
对于 标准布局类型 ,所有非静态数据成员必须具有相同的访问权限。 |
(since 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 { friend void h(Base& b, Derived& d); void f(Base& b, Derived& d) // 派生类的成员函数 { ++d.i; // 正确:d的类型是Derived ++i; // 正确:隐式'*this'的类型是Derived // ++b.i; // 错误:不能通过Base访问受保护成员 // (否则将可能更改其他派生类,比如假设的 // Derived2的基类实现) } }; void Base::g(Base& b, Derived& d) // Base的成员函数 { ++i; // 正确 ++b.i; // 正确 ++d.i; // 正确 } void h(Base& b, Derived& d) // Derived的友元函数 { ++d.i; // 正确:Derived的友元可以通过Derived对象 // 访问受保护成员 // ++b.i; // 错误:Derived的友元不是Base的友元 } 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; // 正确 } };
私有成员访问
私有成员构成了类的实现,同时也为类的其他成员提供了私有接口。
类的私有成员仅对该类的成员和友元可访问,无论这些成员是否位于相同或不同的实例:
class S { private: int n; // S::n 是私有成员 public: S() : n(10) {} // 在 S::S 中可访问 this->n S(const S& other) : n(other.n) {} // 在 S::S 中可访问 other.n };
显式转换 (C风格和函数风格)允许从派生类左值转换为其私有基类的引用,或从派生类指针转换为其私有基类的指针。
继承
参见 派生类 了解public、protected和private继承的含义。
关键词
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1873 | C++98 | 派生类的友元可以访问 protected 成员 | 改为不可访问 |