第 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;
}

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

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

相关推荐
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__1 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word2 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆2 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz2 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE3 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy4 小时前
c++ 笔记
开发语言·c++
fengbizhe4 小时前
笔试-笔记2
c++·笔记
徐霞客3204 小时前
Qt入门1——认识Qt的几个常用头文件和常用函数
开发语言·c++·笔记·qt