第 18 章 模板的多态性

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

静态多态

静态多态优势:

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

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

#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 , 可以在一定程度上弥补静态多态在面对容器容纳异构类型的不足:

#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++ 中静态多态性的可用性,带来了实现经典设计模式的新方法。以桥接模式为例,使用动态多态实现如下:

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

而使用静态多态:

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值