Effective C++ 条款41:了解隐式接口和编译期多态
文章目录
- 条款41:了解隐式接口和编译期多态
- 示例 1:显式接口与运行期多态
- 示例 2:隐式接口与编译期多态
- 示例 3:运行期多态与编译期多态的对比
- 结论
条款41:了解隐式接口和编译期多态
面向对象编程(OOP)和泛型编程(GP)在接口和多态的实现方式上存在显著差异:
-
OOP(运行期多态):
- 接口:显式定义,集中于函数签名。
- 多态:通过
virtual
函数在运行时实现。
-
GP(编译期多态):
- 接口:隐式定义,基于可以在模板中使用的有效表达式。
- 多态:通过模板实例化和函数重载解析在编译时实现。
示例 1:显式接口与运行期多态
显式接口 通过清晰的函数签名定义,运行期多态 则由虚函数表支持:
class Shape { public: virtual void draw() const = 0; // 显式接口 virtual ~Shape() = default; }; class Circle : public Shape { public: void draw() const override { // 运行期多态 std::cout << "Drawing Circle" << std::endl; } }; void render(const Shape& shape) { shape.draw(); // 多态调用 } int main() { Circle c; render(c); // 调用 Circle 的 draw 实现 return 0; }
在这里,Shape
明确定义了接口,Circle
通过运行期多态提供其实现。
示例 2:隐式接口与编译期多态
模板参数的接口是隐式的,只要模板实例化时所需的操作是可用的,代码就可以编译通过。
template<typename T> void render(const T& shape) { shape.draw(); // 隐式接口:假定 T 有成员函数 draw } class Square { public: void draw() const { std::cout << "Drawing Square" << std::endl; } }; int main() { Square s; render(s); // 编译期多态:实例化 render<Square> return 0; }
此例中的模板 render
并未显式要求 T
必须拥有 draw
函数,但只要 T
提供了 draw
,模板就能正常编译。
示例 3:运行期多态与编译期多态的对比
如果需要同时支持运行期和编译期多态,可以通过接口和模板结合实现:
class Drawable { public: virtual void draw() const = 0; // 运行期多态接口 virtual ~Drawable() = default; }; template<typename T> class DrawableWrapper : public Drawable { public: explicit DrawableWrapper(T obj) : object(obj) {} void draw() const override { // 使用编译期多态 object.draw(); } private: T object; }; class Triangle { public: void draw() const { std::cout << "Drawing Triangle" << std::endl; } }; int main() { Triangle t; DrawableWrapper<Triangle> wrappedTriangle(t); wrappedTriangle.draw(); // 通过运行期接口调用编译期多态的实现 return 0; }
结论
- 显式接口和运行期多态 适合需要动态行为变化的场景,但运行时开销较高。
- 隐式接口和编译期多态 适合泛型编程,能在编译期优化性能,但接口定义隐晦,需要注意约束管理。
- 在设计时应根据需求选择适合的多态实现方式,必要时将两者结合使用以平衡灵活性和效率。