C++ / a working model

18 / 80   ·   C++11   ·   约 8 分钟

访问控制 Access control 与继承权限

先记住这句话

public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。

本篇内容
  1. 成员权限与类型默认值
  2. 继承方式改变外部可见性
  3. protected 与查找顺序
  4. 运行示例
  5. 动手练习

成员权限与类型默认值

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 提供维护自身不变量的公开操作,而不是用强制转换规避接口。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录