访问说明符
在 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 | 受保護成員對派生類的友元可訪問 | 不可訪問 |