第 18 章 模板的多态性

多态性也是面向对象泛型编程的基础,C++ 中通过类继承和虚函数来支持多态。因为这些机制 (至少部分) 在运行时处理,所以这里讨论动态多态性。通常所说的多态性,指的就是动态多态性 。 然而,模板还允许将不同的特定行为与单个泛型表示关联起来,但这种关联通常在编译时进行处理, 称之为静态多态性

静态多态

静态多态优势:

  • 编译时多态:静态多态的实现主要依靠模板(Template)技术,编译器在编译阶段就能确定函数或类的具体实现,通过模板参数推导和实例化来生成特定类型的代码。
  • 性能优势:由于编译时就确定了调用哪一个函数版本,所以无需运行时的类型检查和虚函数表查找,理论上可以获得更好的运行时性能。
  • 类型安全性:编译器会在编译阶段检查模板的正确性,错误会在编译阶段捕获,这增强了类型的安全性。
  • 不受继承关系限制:静态多态不需要类之间存在继承关系,只要满足模板参数的要求,就能实现多态性。
  • 灵活性:静态多态允许对内置类型和自定义类型进行同样的处理,不需要类之间共享公共基类。

但是在涉及容器(集合)时,如果要存放不同类型(异构)的对象,如一个集合中同时包含圆形和线段等不同类型的几何对象,静态多态性就显得力不从心。因为在编译时模板参数就必须是确定的类型,不能容纳未知的多种类型。

cpp 复制代码
#include <iostream>
#include <vector>

class Circle {
public:
    int x,y; // 为了体现和Line异构
    void doDraw() const {
        std::cout<<"Circle::doDraw()"<<std::endl;
    }
};

class Line {
public:
    int len; // 为了体现和Circle异构
    void doDraw() const{
        std::cout<<"Line::doDraw()"<<std::endl;
    }
};

template<typename T>
void draw(T const& t) {
    t.doDraw();
}

template<typename T> 
void drawAll(std::vector<T> const& v) {
    for(auto const& e:v) {
        e.doDraw();
    }
}


int main() {
    Circle c{};
    draw(c);
    Line l{};
    draw(l);

    // 但静态多态的一个局限,就是没有办法在容器中存放异构类型的元素
    std::vector<Line> vc{Line{},Line{},Line{}}; 
	drawAll(vc);
    return 0;
}

C++17提供了一个新特性:std::variant , 可以在一定程度上弥补静态多态在面对容器容纳异构类型的不足:

cpp 复制代码
#include <iostream>
#include <variant>
#include <vector>

class Circle {
public:
    int x,y; // 为了体现和Line异构
    void doDraw() const {
        std::cout<<"Circle::doDraw()"<<std::endl;
    }
};

class Line {
public:
    int len; // 为了体现和Circle异构
    void doDraw() const{
        std::cout<<"Line::doDraw()"<<std::endl;
    }
};

template<typename T>
void draw(T const& t) {
    t.doDraw();
}

template<typename T> 
void drawAll(std::vector<T> const& v) {
    for(auto const& e:v) {
        // 使用auto推断e的类型,然后输出
        std::visit([](const auto& arg) { arg.doDraw(); }, e);
    }
}


int main() {
    using Shape = std::variant <Circle,Line>; // 使用variant来解决异构类型在容器中的问题
    std::vector<Shape> vd{Line{}, Circle{}, Line{}};    
    drawAll(vd);
    return 0;
}

静态多态性通常认为比动态多态性更类型安全,因为所有绑定都在编译时检查。例如,模板实 例化的容器中插入错误类型的对象没有危险。然而,需要指向公共基类的指针的容器中,这些指针 有可能无意中指向不同类型的对象。

设计模式的新形式

C++ 中静态多态性的可用性,带来了实现经典设计模式的新方法。以桥接模式为例,使用动态多态实现如下:

cpp 复制代码
#include <iostream>
#include <memory>

class Implementation {
public:
    virtual void do_something() = 0;
};

class ImplA : public Implementation {
public:
    void do_something() override {
        std::cout << "ImplA::do_something()" << std::endl;
    }
};

class ImplB : public Implementation {
public:
    void do_something() override {
        std::cout << "ImplB::do_something()" << std::endl;
    }
};

class Inference {
private:
    Implementation* impl;
public:
    explicit Inference(Implementation* impl): impl(impl) {}
    void do_something() {
        impl->do_something();
    }
};

int main() {
    ImplA a{};
    ImplB b{};
    Inference inference(&a); // 在接口的不同实现之间切换
    inference.do_something();
    inference = Inference(&b); // 在接口的不同实现之间切换
    inference.do_something(); 
    return 0;
}

而使用静态多态:

cpp 复制代码
#include <iostream>
#include <memory>


class ImplA  {
public:
    void do_something() {
        std::cout << "ImplA::do_something()" << std::endl;
    }
};

class ImplB  {
public:
    void do_something() {
        std::cout << "ImplB::do_something()" << std::endl;
    }
};

template<typename T>
class Inference {
private:
    T impl;
public:
    explicit Inference(T const& impl): impl(std::move(impl)) {}
    void do_something() {
        impl.do_something();
    }
};

int main() {
    Inference inference(ImplA{}); 
    inference.do_something();
    Inference inferenceb{ImplB{}};
    inferenceb.do_something();
    return 0;
}

如果在编译时就知道实现的类型,那么可以利用模板的强大功能 ,带来更好的类型 安全性 (部分原因是避免了指针转换) 和性能。

相比于动态多态性,模板利用了静态多态性在编译时解析接口的优势,实现了更高的运行效率,尤其是在处理像容器和迭代器这类场景时,由于迭代器轻量级的特性,采用虚函数机制的成本过高且性能低下。

相关推荐
云泽80812 分钟前
C/C++内存管理详解:从基础原理到自定义内存池原理
java·c语言·c++
小年糕是糕手43 分钟前
【数据结构】双向链表“0”基础知识讲解 + 实战演练
c语言·开发语言·数据结构·c++·学习·算法·链表
将车2441 小时前
C++实现二叉树搜索树
开发语言·数据结构·c++·笔记·学习
Dream it possible!2 小时前
LeetCode 面试经典 150_栈_简化路径(53_71_C++_中等)(栈+stringstream)
c++·leetcode·面试·
爱和冰阔落2 小时前
【C++继承下】继承与友元 / static 菱形继承与虚继承 组合的详解分析
c++·面试·腾讯云ai代码助手
草莓熊Lotso3 小时前
《C++ Stack 与 Queue 完全使用指南:基础操作 + 经典场景 + 实战习题》
开发语言·c++·算法
敲上瘾3 小时前
单序列和双序列问题——动态规划
c++·算法·动态规划
ajassi20003 小时前
开源 C++ QT QML 开发(二十二)多媒体--ffmpeg编码和录像
c++·qt·开源
小糖学代码5 小时前
Linux:11.线程概念与控制
linux·服务器·c语言·开发语言·c++
Larry_Yanan8 小时前
QML学习笔记(四十)QML的ApplicationWindow和StackView
c++·笔记·qt·学习·ui