11 继承与接口
11.1 总则 [class.derived]
除本条款明确修改的部分外,[class.derived]、[class.mi]、 [class.virtual]、[class.member.lookup]、[namespace.udecl],以及 C++ 关于访问控制与 derived-to-base 转换的普通规则,都原样适用于 SCPP26 程序中的继承。
任何用关键字
struct引入的声明,都不得:
(2.1) 带有 base-clause([class.derived]);
(2.2) 被 attribute-token scpp::interface 标记;或者
(2.3) 声明 virtual 成员函数或 virtual 析构函数。
任何命名了某个用关键字
struct声明出来的类型的 base-specifier, 都是不合法的(ill-formed)。- 与 (3) 不会对
struct施加其他限制。struct仍然可以像普通 C++ 规则所允许的那样,声明构造函数、access-specifier、非 static 数据成员 以及非 virtual 成员函数。
- 与 (3) 不会对
当且仅当定义某个 class 的那个声明,在附着于该 class 定义的 attribute-specifier-seq([dcl.attr.grammar])里带有 attribute-token
scpp::interface时,这个 class 才是一个接口 (interface)。凡是用关键字class引入、但没有这样标记的声明,都是 一个普通 class(ordinary class),即使它恰好没有声明任何非 static 数据成员,也仍然如此。如果一个 class 定义的直接 base-specifier-list 中含有多于一个普通 class,那么程序不合法(ill-formed)。一个 class 除了至多一个普通直接 base class 之外,还可以额外拥有任意多个作为直接 base class 的接口。
本条款只通过 (5) 里的接口引入多重继承。它不会以其他方式放宽 SCPP26 现有的“普通实现继承仍然是单继承”的规则。
【注:作为一种风格约定,SCPP26 源码里被
[[scpp::interface]] 标记的 class,推荐用前导 I
来命名,例如 IReader 或 IMovable。这只是一个
非规范性的建议:接口名字不遵循这个约定,本身不会让程序变成不合法。——注释结束】
class [[scpp::interface]] IReader {
public:
virtual ~IReader() = default;
virtual void read() = 0;
};
struct PlainData {
private:
int value{};
public:
PlainData(int v) : value{v} {}
int read() const { return value; }
};
class TagOnly {
public:
virtual ~TagOnly() = default;
void ping();
};
class FileReader : public virtual IReader {
public:
~FileReader() override = default;
void read() override {}
};
class Bad : public FileReader, public TagOnly {
public:
~Bad() override = default;
}; // ill-formed: two ordinary direct base classes under (6)
struct BadStruct : public TagOnly {}; // ill-formed: a struct shall not inherit11.2 接口声明 [dcl.attr.scpp.interface]
一个接口不得声明任何非 static 数据成员。凡是被标记为
[[scpp::interface]]的 class 定义,只要声明了任意类型的非 static 数据成员,该程序就不合法(ill-formed)。- 并不禁止那些不会引入每对象状态的 class-scope 声明,比如类型 别名、枚举、static 数据成员、static 成员函数,或者其他不属于非 static 数据成员的声明。
一个接口的每个直接 base class 自己也都必须是接口。如果某个接口的 任意直接或传递 base class 是普通 class,那么这个接口就是不合法的。
一个接口可以声明 virtual 成员函数;这些函数既可以带函数体,也可以 带 pure-specifier。它也可以声明非 virtual 成员函数。接口里声明的非 virtual 成员函数,不属于该接口的动态派发契约;对它的调用,和普通非 virtual 成员函数完全一样。
【注:本条款没有为接口的构造函数额外引入任何特殊规则。接口可以像普通 class 一样声明构造函数,而 base-class 与 virtual-base 初始化仍然原样 遵循普通 C++ 规则。——注释结束】
- 如果一个程序会在任何“形成对象”的语境里形成一个完整对象,并且它的 most-derived type 就是接口,那么它就是不合法的;这类语境包括:
(5.1) 一个按值变量定义;
(5.2) 一个非 static 数据成员声明;
(5.3) 一个数组元素类型;
(5.4) 一个 new-expression;
(5.5) 一个临时对象;
(5.6) 一个按值类型的函数形参;或者
(5.7) 一个按值返回的函数返回类型。
- 适用时,不受“该接口是否还含有纯虚函数”的影响。即使一个接口 的所有 virtual 成员函数都有默认实现,它也仍然不能被直接实例化。
【注:(5) 防止把实现了接口的对象切片(slicing)成一个独立的接口对象。 通过引用或指针传递、返回接口,仍然是合法的,但仍受普通 C++ 关于引用 绑定、指针转换和访问控制的规则约束。一个更大的 most-derived object 内部的接口 base subobject,本身不属于 (5) 所说的完整对象。——注释结束】
【注:一个含有接口 base subobject 的 most-derived object,在 copy 或 move 时,受 §6.4 与 §6.5 约束;这些小节对 base-class subobject 的处理,也同样适用于这里。——注释结束】
class [[scpp::interface]] ILogger {
static constexpr int version = 1;
public:
virtual ~ILogger() = default;
virtual void log() {
helper();
}
void helper() {}
};
class [[scpp::interface]] IBadState {
int counter{};
public:
virtual ~IBadState() = default;
}; // ill-formed: non-static data member under (1)
class Storage {
public:
virtual ~Storage() = default;
};
class [[scpp::interface]] IBadBase : public virtual Storage {
public:
virtual ~IBadBase() = default;
}; // ill-formed: interface inheriting an ordinary class under (3)
void consume(ILogger& ref); // OK
void copy(ILogger value); // ill-formed: (5.6)
ILogger make_logger(); // ill-formed: (5.7)11.3 Base-specifier 与接口身份 [class.mi]
如果某个 class
D直接继承接口I,那么命名I的那个 base-specifier 必须带virtual关键字。凡是没有写virtual的直接接口 base,程序都不合法。如果某个 class
D直接继承普通 classB,那么命名B的那个 base-specifier 不得带virtual关键字。凡是给直接普通 class base 写了virtual的程序,都是不合法的。- 同时适用于“接口继承接口”和“普通 class 继承接口”这两种情况。
【注:(2) 在 SCPP26 中并没有拿走任何有用的表达能力。按 §11.1,一个 class 至多只有一个普通直接 base class;而按 §11.2 (3),接口又只能继承别的接口。因此,普通 base 之间的关系不可能分叉成 多条路径,也不可能再从多条路径重新汇合,所以普通 C++ 里 virtual inheritance 用来解决的那种“重复 subobject”问题,在 SCPP26 中从结构上 就不可能出现在普通 base 上。——注释结束】
一个接口 base 只能用
public或private作为 access-specifier 来继承。如果某个接口 base 以
public方式继承,那么只要访问控制允许, 到该接口类型的 derived-to-base 转换,对普通外部代码以及派生类内部都 可用,但仍受程序中其他访问规则约束。如果某个接口 base 以private方式继承,那么这种转换只在该派生类自己的成员函数内可用;就本规则而 言,某个嵌套 class 的成员函数不算该派生类自己的成员函数。任意外部 代码若尝试做对应的转换,程序都不合法。对于一个从某个 most-derived object 出发、经由一个或多个继承路径可达 的接口 base
I,只要这些路径按 (1) 都是 virtual 的,那么它的可观察 语义必须与同样源码下普通 C++ virtual inheritance 的语义一致:把这个 most-derived object 通过任意合法路径转换到I,得到的都表示同一个共享 的Ibase 身份;并且,经由I进行 virtual dispatch 时,必须选中唯一 的 final overrider。指向非接口类型的指针或引用,采用 ordinary representation (普通表示)。它占一个 machine word,只表示被引用对象的地址。如果某个 完整的非接口 class 类型
D直接或传递地实现了一个或多个接口,那么 这些接口实现都不得给D带来额外的逐对象存储;特别是,在保持D的 普通 base class 与非 static 数据成员除此之外都相同的前提下,单纯增删 接口 base 不会改变sizeof(D)。指向接口类型的指针或引用,采用 interface representation (接口表示)。它恰好占两个 machine word,因此在同一目标上恰好是 (7) 所要求表示大小的两倍。其中一个 word 表示底层 most-derived object 的 地址,另一个 word 表示该接口的 dispatch 信息,足以对当前 concrete object 上该接口所声明的每个 virtual 成员函数进行 dispatch。
- 所述 dispatch 信息,必须在形成那个接口类型指针或引用值时就被 解析出来。此后,经由该值调用该接口所声明的 virtual 成员函数时,必须 直接使用它携带的 dispatch 信息,不得在调用点对该对象已实现的接口集合 执行搜索。
只有“指向接口的指针类型”才有 null 值。一个由
nullptr、zero- initialization,或者某个指针类型成员/变量的 default-initialization 产生 出来的“指向接口的指针值”,都是一个 null interface pointer。在 null interface pointer 里,对象地址那个 word 为零。dispatch-information 那个 word 的值是未指定的;只要对象地址那个 word 为零,程序语义就不得 依赖那个 word。对一个“指向接口的指针值”做 nullness test——包括和
nullptr比较, 以及上下文到bool的转换——只取决于 (10) 所述对象地址那个 word 是否 为零。dispatch-information 那个 word 在这种测试里不起任何作用。特别是, 两个 null interface pointer 即使携带的 dispatch-information word 不相 等,也仍然都是 null。从接口类型指针或引用值到任何其表示只有一个 machine word 的 scalar type,都不存在隐式或显式转换。这包括
void*、任何其表示只有一个 machine word 的 raw pointer type,以及像uintptr_t或intptr_t这样的整数 scalar type——只要这些类型在该目标上是一个 machine word。 凡是尝试这样转换的程序,都是不合法的。
【注:如果程序必须通过某个只接受
void*、uintptr_t 或其他单 machine-word scalar
的 API 传递一个接口值,那么它可以先把该接口值存入
一个具有稳定存储的对象里,再传递一个指向那段存储的指针,或者其他某种
应用自定义、且能保留所需信息的 handle。任何这样的指针转换,仍然受 §5.1
约束。——注释结束】
- SCPP26 不要求必须用某个特定 C++ 编译器或任何其他实现技术所采用的
同一种 ABI、word 次序、对象布局或 dispatch-table 结构,来实现 (6)、
(8)、(9)、(10) 与 (11) 的保证。接口表示中那两个 machine word 的先后
次序未指定,(8) 所述 dispatch 信息自身的内部结构也未指定。只要这些段
落点名的可观察语义被保留即可:按 (6) 的共享接口身份、按 (7) 的单
machine-word 普通表示、按 (8) 的双 machine-word 接口表示、按 (9)
的无需调用点搜索的正确 dispatch,以及按 (10) 与 (11) 只由对象地址 word
决定的 null interface-pointer 语义。这项许可只适用于本条款中 SCPP26
对“接口继承必须写
virtual”及接口类型表示的实现方式;它不会 改变其他任何 C++ 构造所要求的可观察语义。
【注:因此,像 unique_ptr<I>
这样一个拥有所有权的指针特化——其中 I
是某个接口——可能需要存下接口表示的两个 word,才能在转移所有权时保留
完整的接口值。确切的库实现机制不在本条款范围内。——注释结束】
class [[scpp::interface]] IMovable {
public:
virtual ~IMovable() = default;
virtual void move_it() = 0;
};
class [[scpp::interface]] IFlyable : public virtual IMovable {
public:
~IFlyable() override = default;
};
class [[scpp::interface]] ISwimmable : public virtual IMovable {
public:
~ISwimmable() override = default;
};
class Duck : public virtual IFlyable, public virtual ISwimmable {
public:
~Duck() override = default;
void move_it() override {}
};
class BadDuck : public IFlyable {
public:
~BadDuck() override = default;
}; // ill-formed: direct interface base lacks `virtual`
class OrdinaryBase {
public:
virtual ~OrdinaryBase() = default;
};
class BadVirtualOrdinary : public virtual OrdinaryBase {
public:
~BadVirtualOrdinary() override = default;
}; // ill-formed: direct ordinary-class base uses `virtual`
class SecretMover : private virtual IMovable {
public:
~SecretMover() override = default;
void move_it() override {}
IMovable& expose_inside() { return *this; } // OK: conversion allowed here
};
void take_movable(IMovable&);
void take_userdata(void*);
struct CallbackState {
IMovable* value;
};
void demo(Duck& duck, SecretMover& secret, CallbackState& state) {
take_movable(duck); // OK: public interface inheritance
// take_movable(secret); // ill-formed: private base conversion denied
IMovable* p = nullptr;
if (p) {
p->move_it();
}
state.value = &duck;
// take_userdata(state.value); // ill-formed: interface pointer is not `void*`
// auto bits = uintptr_t(state.value); // ill-formed: not a single-word scalar conversion
take_userdata(&state); // OK: pass pointer to stable storage instead
}11.4 接口成员、名字查找与 virtual dispatch [class.member.lookup], [namespace.udecl], [class.virtual]
派生类中的非限定成员名查找,仍然遵循普通 C++ 规则。如果两个或更多 base class 都让同名成员变得可达,而派生类自己又没有引入能消除歧义的 声明,那么这个名字就像在 C++ 里一样是不明确(ambiguous)的。
对 (1) 里的歧义,不会因为一个候选来自普通 base class、另一个来自 接口,或者因为一个候选是 virtual、另一个不是 virtual,就被静默地自动 消解。
因而,下面每一种情况,除非程序通过普通 C++ 手段显式消解(例如: 使用限定名、在适用处声明一个新的 overriding 成员、或者写一个
using-declaration 把期望的 base 声明引入到派生类里),否则都是歧义:
(3.1) 两个 sibling 接口都为同签名成员函数提供了默认实现;
(3.2) 一个普通 base class 的成员函数,与一个接口成员函数同名;
(3.3) 两个互不相关的 base class 声明了同名但签名不同的重载。
在 (3.3) 所述情况下,只有先把名字查找本身消解为不歧义之后,重载决议 才会开始。派生类里的
using B::f;声明,会像在普通 C++ 中那样,把选中 的 base 声明引入到派生类自己的作用域里;在此之后,再对所得的重载集合 应用普通的重载决议规则。如果两个或更多中间 base class 都重写了同一个共享 virtual base 的 同一个 virtual 函数,而某个 most-derived class 又同时继承了这些中间 base,那么这个 most-derived class 必须自己声明一个 overriding 函数, 作为唯一的 final overrider。限定名或
using-declaration 都不能满足 这个要求。如果 most-derived class 里的某一个声明,确实同时重写了多个 base virtual 函数,那么这个单独的声明可以同时满足 (5) 对这些函数的要求。
class [[scpp::interface]] IPrintable {
public:
virtual ~IPrintable() = default;
virtual void print() {
helper();
}
void helper() {}
};
class [[scpp::interface]] IDebuggable {
public:
virtual ~IDebuggable() = default;
virtual void print() {}
};
class Tool : public virtual IPrintable, public virtual IDebuggable {
public:
~Tool() override = default;
// void print() override {} // one valid explicit resolution
};
class Worker {
public:
virtual ~Worker() = default;
void start() {}
};
class [[scpp::interface]] IStartable {
public:
virtual ~IStartable() = default;
virtual void start() {}
};
class Machine : public Worker, public virtual IStartable {
public:
~Machine() override = default;
// Machine m; m.start(); // ambiguous under (3.2)
};
class [[scpp::interface]] IIntOps {
public:
virtual ~IIntOps() = default;
void f(int) {}
};
class [[scpp::interface]] IDoubleOps {
public:
virtual ~IDoubleOps() = default;
void f(double) {}
};
class CombinedOps : public virtual IIntOps, public virtual IDoubleOps {
public:
~CombinedOps() override = default;
using IIntOps::f;
using IDoubleOps::f;
};
class [[scpp::interface]] ITick {
public:
virtual ~ITick() = default;
virtual void tick() = 0;
};
class [[scpp::interface]] ILeft : public virtual ITick {
public:
~ILeft() override = default;
void tick() override {}
};
class [[scpp::interface]] IRight : public virtual ITick {
public:
~IRight() override = default;
void tick() override {}
};
class Both : public virtual ILeft, public virtual IRight {
public:
~Both() override = default;
void tick() override {}
};11.5 Virtual 析构与显式 override [class.dtor], [class.virtual]
每一个 class 都必须显式声明析构函数,并且这个析构函数必须是 virtual。凡是违背这条规则的完整 class 定义,都是不合法的。
- 一体适用于以下所有情形:该 class 不论是否声明或继承任何其他 virtual 成员函数,不论是否实现任何接口,也不论是否已经被立即拿去当作 base class 使用。
SCPP26 不会隐式合成、提升或者重新解释一个析构函数使之成为 virtual。如果程序员没有显式声明这个 virtual 析构函数,那么程序就是 不合法的。
如果某个成员函数声明或析构函数声明重写(override)了任意 base class 的 virtual 成员函数或析构函数,那么这个声明必须带
overridevirt-specifier。凡是真正发生了 override 却省略了override的程序,都是不合法的。如果某个声明写了
overridevirt-specifier,但它事实上并没有重写 任何 base virtual 成员函数或析构函数,那么程序就像在普通 C++ 中那样 不合法。- 对析构函数没有任何例外。凡是重写了 virtual base 析构函数的
派生类析构函数,都必须写成类似
~D() override = default;或者~D() override { ... }这样的形式。
- 对析构函数没有任何例外。凡是重写了 virtual base 析构函数的
派生类析构函数,都必须写成类似
using-declaration 不是一个 overriding 声明;它本身既不会满足, 也不会违反 (4)。
【注:通过对每个 class 都要求满足 (1),SCPP26 消除了“某个 class 以后
才被拿去当作 base class 使用、却缺少 virtual 析构函数”这种潜伏缺陷。 而
§11.1
下的 struct,则是那个永远不参与继承和 virtual dispatch
的构造;它仍然可 以封装数据与行为,但不会引入隐藏的 virtual-dispatch
状态。凡是某个 class 在强制性的析构函数之外还额外声明 virtual
成员函数时,它就是在
做一个有意识的、与继承有关的设计选择。一个附带的结果是:对已经满足 (1)
的 class 来说,后续再添加 virtual 成员函数或接口 base,也不会
“新引入”这类状态。——注释结束】
【注:按 (1) 必须显式写出的析构函数,是一个 user-declared destructor。 因而,SCPP26 已经在其他地方规定给“user-declared destructor”带来的 隐式 copy 构造和 copy 赋值后果,会对接口像对其他任何 class 一样照常 生效;见 §6.5。本条款不会为接口引入任何 例外。——注释结束】
class Base {
public:
virtual void run() {}
virtual ~Base() = default;
};
class Derived : public Base {
public:
~Derived() override = default;
void run() override {}
};
class MissingDtor {
public:
void ping() {}
}; // ill-formed: every class needs an explicit virtual destructor
class MissingOverride : public Base {
public:
virtual ~MissingOverride() = default; // ill-formed: overrides `Base::~Base` but omits `override`
void run() {} // ill-formed: overrides `Base::run` but omits `override`
};
struct Packet {
private:
int value{};
public:
Packet(int v) : value{v} {}
int read() const { return value; }
};
struct BadStructVirtual {
virtual ~BadStructVirtual() = default;
}; // ill-formed: a struct shall not declare virtual members← 上一节:迭代语句 · 目录