当前位置: 首页 > news >正文

C++接口继承和实现继承

C++的继承

继承的概念是从父哪里得到了什么,儿子继承了父亲的财产,这偏重于特性的继承和代码的复用。而C++类的继承的更关注的应该是对象概念的泛化和实现,即父类是一般性,子类是特化,每一个子类都拥有父类一样的特性。C++中继承的关系表示的是is a的关系,即Derive is a kind of Base,里氏替换原则正是基于这一点,任何用父类作为参数的地方,均应该能用子类实现替换。

在实际开发过程中,我们们常常能够见到两种关于继承使用的范式:

一种是基类定义接口,子类重写实现接口

// 接口类(抽象基类)
class Drawable {
public:
    virtual void draw() const = 0;  // 纯虚函数
    virtual ~Drawable() = default;  // 虚析构函数
};

// 实现类
class Circle : public Drawable {
public:
    void draw() const override { 
        std::cout << "Drawing Circle\n"; 
    }
};

另一种是,将公共逻辑提升到基类中,子类可复用基类函数

// 带实现的基类
class Shape {
protected:
    std::string color;
public:
    explicit Shape(std::string c) : color(std::move(c)) {}   
    void printInfo() const {
        std::cout << "Color: " << color << "\n";
    }
};

// 派生类,可以直接使用基类的printInfo函数
class Square : public Shape {
public:    
   Square(){}
};

这两种继承范式在实际中往往并未分离,而是糅合在一起。这里其实引出了我们今天要探讨的话题:接口继承实现继承

接口继承(Interface Inheritance)

接口继承强调的是提供接口而不是具体实现。在接口继承中,基类仅声明成员函数(通常是纯虚函数),而不提供这些函数的具体实现。派生类需要实现基类中声明的这些函数。

  • 基类提供一个接口(声明),派生类实现这个接口。
  • 基类中的成员函数通常是纯虚函数(即没有实现)。
  • 接口继承是用来定义行为的规范,而不是实现。
// 定义接口类
class IShape {
public:
    virtual ~IShape() = default;
    virtual void draw() const = 0;  // 纯虚函数,派生类必须实现
    virtual double area() const = 0; // 纯虚函数,派生类必须实现
};

实现继承(Implementation Inheritance)

实现继承指的是派生类不仅继承了基类的接口,还继承了基类的实现。在这种情况下,基类提供了成员函数的实现,派生类可以选择使用基类的实现,或者重写这些实现。

  • 基类提供了实现,派生类继承这些实现。
  • 如果派生类需要改变实现,可以重写基类的方法(覆盖)。
  • 实现继承通常用于共享代码,而不是仅仅共享接口。
// 基类提供实现
class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() const {
        std::cout << "Drawing a Shape" << std::endl;
    }
    virtual double area() const = 0; // 仍然是纯虚函数,要求派生类实现
};

使用场景

接口继承:接口继承通常用于定义API、插件架构、以及需要保证一致性的系统。例如,GUI框架中,Drawable 接口可以由不同的图形对象(如 Circle, Rectangle 等)实现。

实现继承:实现继承通常用于减少代码重复,尤其是在多个类共享相似行为时。比如,Shape 基类可以提供一些通用方法(如 draw()),而子类可以重用或覆盖这些方法。

C++和Java的对比

从Java转到C++的程序员,对这两种形态可能并不陌生,这不就是Java的Interface和Extends吗?可以看如下对比

特性

C++

Java

接口定义

含纯虚函数的抽象类

interface

关键字

实现继承

普通类继承(可含具体实现)

extends

继承类

接口继承

继承纯虚类

implements

实现接口

多重继承

支持(类/抽象类均可)

类单继承,接口多实现

默认实现

基类可直接提供

Java 8+ 接口支持default

方法

抽象方法声明

virtual func() = 0

abstract

关键字

构造方法继承

不自动继承,需显式调用

子类必须调用父类构造器

类继承(extends表达 "是什么"(is-a)的层次关系

class FileInputStream extends InputStream { /*...*/ } // 是输入流的一种

接口实现(implements表达 "能做什么"(can-do)的能力描述 java复制

class ArrayList implements List, Serializable { /*...*/ } // 具备列表能力和序列化能力

C++多接口继承

行为组合(Behavior Composition)

当你希望将一个类的不同职责分离到多个接口中,并允许类根据需要实现这些接口时,可以使用多个接口继承。例如,一个图形系统中的类可能既是“可绘制”的(Drawable),也是“可移动”的(Movable),这种设计可以让你灵活组合不同的行为。
 

class Drawable {
public:
    virtual void draw() const = 0;
};

class Movable {
public:
    virtual void move() = 0;
};

class Printable{
public:
    virtual void print() = 0;
}

class Shape : public Drawable, public Movable, public Printable {
public:
    void draw() const override {
        std::cout << "Drawing shape" << std::endl;
    }
    void move() override {
        std::cout << "Moving shape" << std::endl;
    }
    void pring() override {
        std::cout << "Print shape" << std::endl;
    }
};

我们来讨论一下这种多接口继承风格的代码,这种风格的代码在C++中似乎比较少见,其优缺点如下:

优点:

模块化和解耦

  • 继承多个接口可以将不同功能模块的行为进行分离,使得每个接口关注一个单独的责任或行为。这样,你可以在类中实现不同的功能,而不需要继承包含多种功能的庞大类。
  • 例如,一个 Drawable 接口和一个 Movable 接口可以分别定义绘制和移动行为,Shape 类继承这两个接口,既能绘制也能移动。

提高灵活性和可扩展性

  • 通过多个接口继承,你可以轻松添加新的行为,而不需要改变现有类的结构。例如,增加一个 Resizable 接口,使得某些类能够支持调整大小,而不必修改已有的基类实现。
缺点:

多重继承带来的复杂性

  • C++ 允许类继承多个接口,但多个接口继承也可能导致一些复杂性,尤其是当接口中有相同名称的成员函数时,可能会引发二义性问题(比如多个接口中有相同的函数签名)。C++ 的虚继承和命名空间等机制可以解决一些问题,但可能会使得代码更加复杂。

难以维护

  • 继承多个接口的类可能会变得非常复杂,尤其是当接口数量增多时,类的行为可能会变得难以跟踪和管理。每个接口都定义了某种行为,组合多个接口时,可能会导致类的设计变得难以理解和维护。

潜在的内存开销

  • 在一些情况下,多个接口继承可能会增加内存开销,因为每个接口可能会引入虚表(vtable)和相关的虚函数表项。

更优雅的组合方式

组合优于继承:如果多个接口并不是直接相关,考虑使用组合而不是继承。可以将多个接口作为类的成员,类通过委托的方式实现其行为。组合在很多情况下比继承更加灵活且易于管理。

class Drawable {
public:
    virtual void draw() const = 0;
};

class Movable {
public:
    virtual void move() = 0;
};

class Shape {
private:
    Drawable* drawable;
    Movable* movable;

public:
    Shape(Drawable* d, Movable* m) : drawable(d), movable(m) {}

    void draw() const {
        drawable->draw();
    }

    void move() {
        movable->move();
    }
};

总结

  • 在 C++ 中,继承多个接口可以提供很好的灵活性和模块化,符合接口隔离原则(ISP),尤其适用于行为组合解耦设计。
  • 然而,过度使用多重接口继承可能会增加代码复杂性,导致维护困难。因此,应谨慎使用,特别是接口数量较多时。
  • 在某些情况下,组合可能是更好的选择,尤其是在不同功能之间并没有强耦合关系时。总之,继承多个接口的风格可以在合适的场景下带来灵活性和可扩展性,但需要在设计中保持清晰的结构和合理的接口层次。

相关文章:

  • 观察者模式原理详解以及Spring源码如何使用观察者模式?
  • ChatGLM
  • 前端函数在开发环境与生产环境中处理空字符串的差异及解决方案
  • 为什么WP建站更适合于谷歌SEO优化?
  • Mathtype安装入门指南
  • WPF9-数据绑定进阶
  • 基于 GEE 的 2019 - 2024 年研究区大气污染物浓度月度变化趋势(CO、NO₂、SO₂、O₃ 、HCHO)
  • Linux中的权限问题(二)
  • 压力传感器
  • L1-043 阅览室
  • 【基础架构篇六】《DeepSeek显存管理黑科技:OOM错误终极解决方案》
  • JUC并发一
  • vue3 + thinkphp 接入 七牛云 DeepSeek-R1/V3 流式调用和非流式调用
  • 怎麼防止爬蟲IP被網站封鎖?
  • rustdesk编译修改名字
  • JavaScript系列(76)--浏览器API深入
  • Ubuntu学习备忘
  • 在本地成功部署 AlphaFold 3:完整指南
  • 数据库提权总结
  • 机器学习入门实战 1 - 认识机器学习
  • 上海工匠学院首届学历班56人毕业,新一届拟招生200人
  • 乘客被地铁厕所门砸伤,南京地铁:突然坏的,已和乘客沟通处理
  • 长江画派创始人之一、美术家鲁慕迅逝世,享年98岁
  • 上海杨浦:优秀“博主”购房最高可获200万补贴
  • 两部门发布山洪灾害气象预警:北京西部、河北西部等局地山洪可能性较大
  • “80后”计算机专家唐金辉已任南京林业大学副校长