c++接口必须声明至少一个纯虚函数(virtual void f() = 0),且不可提供实现;析构函数须为virtual;子类实现须用override,终点类可加final;仅靠头文件+inline无法替代虚函数机制。

怎么用 class 声明一个真正不可实例化的接口
不能靠注释或命名约定,C++ 接口必须由编译器强制约束。关键不是“叫 Interface”,而是至少有一个 virtual 函数被声明为 = 0,且类中没有定义该函数的实现。
常见错误是只写 virtual void func();(没加 = 0),结果类仍可实例化;或者写了 = 0 却在类内提供了函数体(语法错误)。
- 纯虚函数必须写成:
virtual void draw() = 0;,不能带函数体 - 构造函数可以有,但必须是
protected(否则派生类无法调用) - 析构函数也应声明为
virtual,否则通过基类指针 delete 派生对象会未定义行为
override 和 final 在接口继承中为什么不能省
不加 override,编译器不会检查你是否真的重写了基类的纯虚函数——拼错函数名、参数类型差一个 const 或引用符,都会导致子类仍含未实现的纯虚函数,从而无法实例化,且错误提示往往指向子类构造,而非函数签名不匹配。
final 不是装饰,它阻止进一步派生,适用于你明确不希望用户再继承该实现类的场景(比如标准库中的 std::string_view 类型策略)。
立即学习“C++免费学习笔记(深入)”;
- 子类实现时务必加
override:例如void draw() override { ... } - 若确定该实现类是终点,加
final:例如class Circle : public Shape final { ... }; - 漏掉
override可能导致静默失败:子类看似实现了接口,实则只是新增了一个同名函数
为什么不能只靠头文件 + inline 实现来“模拟”接口
有人试图在头文件里写一个只有声明的 class,再另起一个 inline 函数集合“假装”是接口。这绕不开 C++ 的 ODR(One Definition Rule)和链接模型:接口的本质是运行时多态,依赖 vtable 和虚函数调用机制,不是函数集合的静态绑定。
这种写法无法支持 std::unique_ptr<shape></shape> 持有不同子类对象,也无法用 dynamic_cast 安全下转型,更无法参与模板约束(如 requires std::derived_from<t shape></t>)。
- 虚函数调用开销虽小,但它是多态的唯一标准路径;用函数指针或
std::function模拟,失去类型安全和编译期检查 - 头文件里只声明抽象类,实现放在 .cpp 中——这是常规做法,不是限制,而是避免模板膨胀和隐式内联带来的二进制兼容风险
- 接口类本身不应含数据成员(或仅含
static constexpr),否则破坏“纯粹行为契约”的语义
抽象类作为模板参数时,std::is_abstract_v 和 std::is_polymorphic_v 怎么选
二者用途完全不同:std::is_abstract_v<t></t> 判断 T 是否为抽象类(即含未实现的纯虚函数),而 std::is_polymorphic_v<t></t> 判断 T 是否至少有一个 virtual 函数(包括虚析构函数)——后者是运行时类型识别(RTTI)的前提。
如果你在写泛型容器,想约束只能传入多态类型(以便用 dynamic_cast),该用 std::is_polymorphic_v;如果做编译期断言防止用户误传具体类当接口用,则用 std::is_abstract_v。
- 接口类一定满足
std::is_polymorphic_v,但反之不成立(比如只有虚析构的类不是抽象类) - 使用
static_assert时注意:抽象类无法作为模板实参直接实例化,所以断言要放在 SFINAE 或 requires 子句之后 - Clang 和 GCC 对
std::is_abstract_v的处理一致,但 MSVC 在早期版本对空基类优化可能影响结果,建议测试实际编译器版本
= 0 只是开始,不是结束。









