18 / 80 · C++11 · 约 8 分钟
访问控制 Access control 与继承权限
public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。
成员权限与类型默认值
public 面向普通调用者,private 只允许声明该成员的类及其友元访问,protected 额外向派生类及其友元开放受限制的访问。类成员可以访问同类其他对象的私有成员,因此权限按类和使用上下文判断,不是每个对象拥有独立的访问名单。
class 默认成员权限和默认继承方式都是 private,struct 则都是 public;两者都能定义构造函数、模板和虚函数。friend 是明确授权,不会自动传递、继承或变成双向关系,适合确有紧密协作的少量函数。
继承方式改变外部可见性
公开继承保留基类 public 与 protected 成员的相应可访问性;受保护继承使这两类成员在派生类中作为 protected 访问;私有继承则作为 private 访问。基类原有 private 成员仍存在于基类子对象内,但派生类不能直接命名它们。
公开且无歧义的继承允许外部把派生类指针隐式转换成基类指针;私有继承不允许普通外部调用者做这种转换。示例用类型特征说明,两种类都有基类关系,却不都能在外部转换,这正是实现复用与公开子类型的差别。
protected 与查找顺序
派生类成员访问基类的 protected 非静态成员时,对象表达式通常必须具有该派生类或其进一步派生类的类型。派生类可以修改另一个同类对象的受保护字段,却不能拿一个任意 Base& 当成通往所有基类对象的后门。
名字查找和重载决议先确定候选及最佳匹配,再执行访问检查。若最佳匹配是 private,编译器报错,不会退回一个匹配更差的 public 重载。排查访问错误时,应先看实际选中了哪个声明,再检查当前上下文是否有权使用它。
容易答错的地方
- 私有继承没有删除基类成员,只是限制外部接口与转换;不要据此推断对象布局。
- private 虚函数仍可被派生类覆盖;覆盖关系与调用表达式的访问检查是两件事。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <type_traits>
class Base {
int secret_ = 7;
protected:
int count_ = 0;
public:
int secret() const { return secret_; }
};
class PublicChild : public Base {
public:
void set_peer(PublicChild& peer, int value) { peer.count_ = value; }
int count() const { return count_; }
};
class PrivateChild : private Base {
public:
int exposed_secret() const { return secret(); }
};
int main() {
static_assert(std::is_base_of<Base, PrivateChild>::value, "base exists");
static_assert(std::is_convertible<PublicChild*, Base*>::value, "public");
static_assert(!std::is_convertible<PrivateChild*, Base*>::value, "private");
PublicChild first, second;
first.set_peer(second, 9);
assert(second.count() == 9);
PrivateChild hidden;
assert(hidden.exposed_secret() == 7);
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread objects-access.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
在 PublicChild 中写 void inspect(Base& b) { b.count_ = 1; } 是否合法?怎样修正?
查看参考答案
不合法。虽然函数属于派生类,但 b 的静态类型是 Base,不能满足这里 protected 对象访问的限制。若操作本来只针对同类对象,把参数改为 PublicChild&;若确实允许修改任意 Base,应由 Base 提供维护自身不变量的公开操作,而不是用强制转换规避接口。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。