编译期多态与CRTP
C++有编译期与运行期,
对于多态则有 编译期多态,运行时多态。
而对于运行时多态,主要是依赖我们熟知的 vptr实现。
本文讲解一下 静态多态与CRTP
静态多态
静态多态在编译期就确定了调用哪个具体函数
实现方式:主要依赖于 函数重载与模板实例化
特点:
- 无运行时的开销,即零成本开销。
- 但同时也缺乏运行时的灵活性
函数重载
函数重载在编译期,通过 名称修饰(Name Mangling)机制实现:
将相同的函数名 与 不同的参数列表 还可以加上 cv限定(const,volatile) 组合成唯一的底层符号名
注意:返回值不参与
例如:
void f(int) 可能被修饰为 _Z1fi;
void f(double)则可能被修饰为 _Z1fd;
重载决议过程:
- 寻找同名且作用域可见的所有函数
- 过滤出参数数量匹配且类型可以隐式转换的函数
- 按 一套优先级标准 进行排序选择
模板实例化
模板实例化即 编译时 编译器会根据 T 类型的不同 生成对应不同的代码
template<typename T>
void Draw(const T& obj)
{
// 编译期必须确保 T 有draw()方法 –上述说的不灵活的地方
obj.draw();
}
class Circle
{
public:
void draw() const {/**/}
};
class Square
{
public:
void draw() const {/**/}
};
编译器 根据实际的 T类型,自动生成 对应的 Draw<Circle> 和 Draw<Square>
CRTP
可以看作是 静态多态的 “虚函数”,但是没有虚函数表的开销
语法: 派生类将自身作为模板参数传递给基类
实现过程:
- 基类先定义通用的 接口骨架
- 然后在内部 通过类型转换 转换成派生类
- 接着调用派生类的具体接口实现
假设我们要实现一个通用的Draw接口
静态多态 CRTP
// 静态多态 CRTP
template<typename Derived>
class Drawable
{
public:
void draw() const
{
// 转换 调用
static_cast<const Derived*>(this)->draw_impl();
}
};
class Circle:public Drawable<Circle>
{
public:
void draw_impl()const {/**/}
};
class Square : public Drawable<Square>
{
public:
void draw_impl() const { /* 画方的具体逻辑 */ }
};
template<typename T>
void render(const Drawable<T>& d)
{
d.draw();
}
而动态多态vptr:
// 动态多态:
class Drawable
{
public:
virtual void draw()const =0;
}
class Circle:public Drawable
{
public:
void draw() const override {/**/}
};
void render(const Drawable&d)
{
// 运行时查询 vtable
d.draw();
}



