静态多态
避免虚调用开销
静态多态 是 CoddyKit 上的免费 C++ Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C++ Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C++ Academy 课程共包含 4 节课。
两种多态
C++ 支持两种多态形式。
- 动态多态:通过虚函数表在运行时解析虚函数
- 静态多态:在编译期解析模板和 CRTP
静态多态以灵活性换取速度。
虚函数的代价
虚函数调用需要通过虚函数表进行间接跳转。编译器通常无法将其内联,从而阻碍许多优化。
#include <iostream>
struct Shape {
virtual double area() const = 0;
virtual ~Shape() = default;
};
struct Square : Shape {
double s;
Square(double x) : s(x) {}
double area() const override { return s * s; }
};
int main() {
Shape* p = new Square(3);
std::cout << p->area() << "\n";
delete p;
return 0;
}使用 CRTP 进行静态分派
使用 CRTP 时,基类通过编译期转换直接调用派生类的方法,因此编译器可以将所有内容内联。
#include <iostream>
template <typename T>
struct Shape {
double area() const { return static_cast<const T*>(this)->area(); }
};
struct Square : Shape<Square> {
double s;
Square(double x) : s(x) {}
double area() const { return s * s; }
};
int main() {
Square sq(3);
std::cout << sq.area() << "\n";
return 0;
}转发函数命名
当基类和派生类都定义了 area 时,请为基类的转发函数使用不同的名称,例如 compute,以便明确调用派生类的 implementation。
#include <iostream>
template <typename T>
struct Shape {
double compute() const { return static_cast<const T*>(this)->area(); }
};
struct Circle : Shape<Circle> {
double r;
Circle(double x) : r(x) {}
double area() const { return 3.14159 * r * r; }
};
int main() {
Circle c(2);
std::cout << c.compute() << "\n";
return 0;
}作为静态多态的模板
普通函数模板同样属于静态多态:只要类型提供所需的接口,相同的代码就能适用于任何类型(编译期的鸭子类型)。
#include <iostream>
struct Dog { void speak() const { std::cout << "Woof\n"; } };
struct Cat { void speak() const { std::cout << "Meow\n"; } };
template <typename Animal>
void make_speak(const Animal& a) { a.speak(); }
int main() {
make_speak(Dog{});
make_speak(Cat{});
return 0;
}类型已知时
静态多态仅在编译时已知具体类型时才能使用。如果您必须将不同类型的对象存储在同一个容器中,并在运行时选择行为,那么仍然需要虚函数。
不支持异构容器
您不能将 Shape<Square> 和 Shape<Circle> 作为同一个公共基类放入同一个 std::vector,因为它们是不相关的类型。这是静态多态的核心限制。
性能优势
通过内联分派调用,编译器可以折叠常量,并完全消除函数调用开销。在针对单一已知类型的紧凑循环中,静态多态的速度可能会显著更快。
#include <iostream>
template <typename T>
struct Op {
int apply(int x) const { return static_cast<const T*>(this)->apply(x); }
};
struct Doubler : Op<Doubler> {
int apply(int x) const { return x * 2; }
};
int main() {
Doubler d;
int sum = 0;
for (int i = 0; i < 5; ++i) sum += d.apply(i);
std::cout << sum << "\n";
return 0;
}组合使用两者
您可以在热点路径中使用 CRTP,并在需要类型擦除时使用一个轻量的虚包装器。这样既能在关键位置获得速度,也能在需要时保留灵活性。
静态接口检查
如果派生类忘记实现所需的方法,错误会在实例化基类转发函数时于编译时出现,而不是等到运行时才出现。
#include <iostream>
template <typename T>
struct Greeter {
void greet() const { static_cast<const T*>(this)->hello(); }
};
struct English : Greeter<English> {
void hello() const { std::cout << "Hello\n"; }
};
int main() {
English{}.greet();
return 0;
}选择合适的工具
请在以下情况下使用静态多态:
- 类型在编译时已知
- 性能至关重要
- 不需要公共的运行时基类型
否则,请优先使用虚函数,以获得更清晰的代码和更好的灵活性。
快速检查
请选择静态多态的关键权衡。
回顾
您比较了静态多态和动态多态。
- 虚函数 = 运行时分派、虚函数表、异构容器
- CRTP/模板 = 编译时分派、可内联、速度更快
- 静态多态要求类型在编译时已知
- 请根据灵活性与性能需求进行选择
常见问题解答
「静态多态」课时是免费的吗?
是的 — 「静态多态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C++ Academy 课程的其余内容,请升级到 CoddyKit PRO。 C++ Academy 课程共包含 4 节课。
「静态多态」这节课中我会学到什么?
避免虚调用开销 你通过在浏览器中直接运行的动手代码来练习 C++ Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C++ Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C++ Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「静态多态」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C++ Academy 课中编写并运行代码吗?
能。每节 C++ Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。