访问说明符
在 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 起)的成员以声明顺序增加。 |
(C++23 前) |
|
对于标准布局类型,所有非静态数据成员必须具有相同访问。 |
(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; // 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 h(Base& b, Derived& d) // Derived 的友元
{
++d.i; // OK: 派生类的友元可以通过派生类的对象访问受保护成员
// ++b.i; // 错误: 派生类的友元并非基类的友元
}
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 风格与函数风格)允许从派生类左值转型到它的私有基类的引用,或从指向派生类的指针转型到指向它的私有基类的指针。
继承
公开、受保护和私有继承的含义,见派生类。
关键词
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
| 缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1873 | C++98 | 受保护成员对派生类的友元可访问 | 不可访问 |