虽然C++没有直接提供interface,但是却提供了虚函数、模板类型等各种语法,使得我们可以用各种方式实现多态,这里我们不区分动态多态和静态多态,而是从设计一个框架的角度,分别使用四种方案实现:
- 虚函数(最简单直接的方式)
std::function
- CRTP(最晦涩的方式)
deducing this
(可以视作CRTP的简化,不再需要将派生类作为模板参数传递,要求C++23)
需求
我们考虑这样一个需求:
- 基类A包括:(不可实例化)
- 主方法run:调用func1,func2和func3
- 实现方法func1
- 实现方法func2(多态,允许子类修改)
- 实现方法func3(多态,子类必须实现)
- 派生类B1:继承A
- 派生类B2:继承A
- 实现方法func3(多态,允许子类修改):调用func4
- 实现方法func4(多态,允许子类修改)
- 具体类C1:继承B2
- 具体类C2:继承B2
最终我们直接通过各种对象自身来调用run方法,达到如下效果:
1 2 3 4 5 6 7 8
| Running B1 < A A::func1 A::func2 B1::func3 Running B2 < A A::func1 A::func2 B2::func3 B2::func4 Running C1 < B2 < A A::func1 A::func2 B2::func3 C1::func4 Running C2 < B2 < A A::func1 A::func2 C2::func3
|
并且确保:A不可实例化,未实现A预留的func3接口时会报错(最好在编译期报错)。
虚函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <iostream>
class A { public: virtual ~A() = default;
void run() { func1(); func2(); func3(); }
void func1() { std::cout << "A::func1 "; }
virtual void func2() { std::cout << "A::func2 "; }
virtual void func3() = 0; };
class B1 : public A { public: void func3() override { std::cout << "B1::func3 "; } };
class B2 : public A { public: void func3() override { std::cout << "B2::func3 "; func4(); }
virtual void func4() { std::cout << "B2::func4 "; } };
class C1 : public B2 { public: void func4() override { std::cout << "C1::func4 "; } };
class C2 : public B2 { public: void func3() override { std::cout << "C2::func3 "; } };
int main() { std::cout << "virtual function:";
std::cout << "\nRunning B1 < A\n"; B1{}.run();
std::cout << "\nRunning B2 < A\n"; B2{}.run();
std::cout << "\nRunning C1 < B2 < A\n"; C1{}.run();
std::cout << "\nRunning C2 < B2 < A\n"; C2{}.run();
return 0; }
|
运行结果 1 2 3 4 5 6 7 8 9
| virtual function: Running B1 < A A::func1 A::func2 B1::func3 Running B2 < A A::func1 A::func2 B2::func3 B2::func4 Running C1 < B2 < A A::func1 A::func2 B2::func3 C1::func4 Running C2 < B2 < A A::func1 A::func2 C2::func3
|
说明:继承自基类A的类型必须实现纯虚函数func2,否则无法实例化,编译报错。
std::function
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <functional> #include <iostream>
using Func = std::function<void()>;
class A { protected: Func m_func1 = [] { std::cout << "A::func1 "; }; Func m_func2 = [] { std::cout << "A::func2 "; }; Func m_func3;
public: void run() { m_func1(); m_func2(); m_func3(); } };
class B1 : public A { public: B1() { m_func3 = [] { std::cout << "B1::func3 "; }; } };
class B2 : public A { public: Func m_func4 = [] { std::cout << "B2::func4 "; };
B2() { m_func3 = [this] { std::cout << "B2::func3 "; m_func4(); }; } };
class C1 : public B2 { public: C1() { m_func4 = [] { std::cout << "C1::func4 "; }; } };
class C2 : public B2 { public: C2() { m_func3 = [] { std::cout << "C2::func3 "; }; } };
int main() { std::cout << "std::functional:";
std::cout << "\nRunning B1 < A\n"; B1{}.run(); std::cout << "\nRunning B2 < A\n"; B2{}.run();
std::cout << "\nRunning C1 < B2 < A\n"; C1{}.run();
std::cout << "\nRunning C2 < B2 < A\n"; C2{}.run();
return 0; }
|
运行结果如下 1 2 3 4 5 6 7 8 9
| std::functional: Running B1 < A A::func1 A::func2 B1::func3 Running B2 < A A::func1 A::func2 B2::func3 B2::func4 Running C1 < B2 < A A::func1 A::func2 B2::func3 C1::func4 Running C2 < B2 < A A::func1 A::func2 C2::func3
|
说明:如果没有对基类A的m_func3赋值,会在运行时抛异常。
CRTP 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include <iostream>
template <typename D> class A { public: void run() { derived().func1(); derived().func2(); derived().func3(); }
void func1() { std::cout << "A::func1 "; }
void func2() { std::cout << "A::func2 "; }
protected: constexpr const D &derived() const { return static_cast<const D &>(*this); }
constexpr D &derived() { return static_cast<D &>(*this); } };
class B1 : public A<B1> { public: void func3() { std::cout << "B1::func3 "; } };
template <typename D> class B2 : public A<D> { public: void func3() { std::cout << "B2::func3 "; derived().func4(); }
void func4() { std::cout << "B2::func4 "; }
protected: constexpr const D &derived() const { return static_cast<const D &>(*this); }
constexpr D &derived() { return static_cast<D &>(*this); } };
class C1 : public B2<C1> { public: void func4() { std::cout << "C1::func4 "; } };
class C2 : public B2<C2> { public: void func3() { std::cout << "C2::func3 "; }
void func4() { std::cout << "C2::func4 "; } };
int main() {
std::cout << "CRTP:";
std::cout << "\nRunning B1 < A\n"; B1{}.run();
std::cout << "\nRunning C1 < B2 < A\n"; C1{}.run();
std::cout << "\nRunning C2 < B2 < A\n"; C2{}.run();
return 0; }
|
运行结果如下 1 2 3 4 5 6 7
| CRTP: Running B1 < A A::func1 A::func2 B1::func3 Running C1 < B2 < A A::func1 A::func2 B2::func3 C1::func4 Running C2 < B2 < A A::func1 A::func2 C2::func3
|
说明:
- 如果派生类没有实现func3,会在编译期报错。
- CRTP只有最底层的类可以实例化。
Deducing this 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream>
class A { public: template <typename Self> void run(this Self &&self) { self.func1(); self.func2(); self.func3(); }
void func1() { std::cout << "A::func1 "; }
template <typename Self> void func2(this Self &&self) { std::cout << "A::func2 "; } };
class B1 : public A { public: void func3() { std::cout << "B1::func3 "; } };
class B2 : public A { public: template <typename Self> void func3(this Self &&self) { std::cout << "B2::func3 "; self.func4(); }
template <typename Self> void func4(this Self &&self) { std::cout << "B2::func4 "; } };
class C1 : public B2 { public: void func4() { std::cout << "C1::func4 "; } };
class C2 : public B2 { public: void func3() { std::cout << "C2::func3 "; } };
int main() { std::cout << "deducing this:";
std::cout << "\nRunning B1 < A\n"; B1{}.run(); std::cout << "\nRunning B2 < A\n"; B2{}.run();
std::cout << "\nRunning C1 < B2 < A\n"; C1{}.run();
std::cout << "\nRunning C2 < B2 < A\n"; C2{}.run();
return 0; }
|
运行结果如下 1 2 3 4 5 6 7 8 9
| deducing this: Running B1 < A A::func1 A::func2 B1::func3 Running B2 < A A::func1 A::func2 B2::func3 B2::func4 Running C1 < B2 < A A::func1 A::func2 B2::func3 C1::func4 Running C2 < B2 < A A::func1 A::func2 C2::func3
|
说明:
- 如果派生类没有实现func3,会在编译期报错。
- deducing this 实现的方案与CRTP不同,中间的基类也可以实例化。
对比小结
几种方案的对比如下表
虚函数 |
动态多态 |
虚表开销 |
较弱(运行时报错) |
基于继承体系 |
std::function |
动态多态 |
取决于对象封装 |
中等(运行时报错) |
适合事件回调、策略模式 |
CRTP |
静态多态 |
无 |
强(编译期检查) |
代码复杂,可读性太低 |
deducing this |
静态多态 |
无 |
强(编译期检查) |
类似 CRTP,但更简洁,要求C++23 |
因此:
- 对计算性能敏感时,优先考虑 CRTP 或 deducing
this;
- 更关注代码的灵活性和可读性,优先考虑虚函数或std::function。
由于编译器优化的存在,这几种方案的运行时效率差异也与实际情景有关。
补充
C++实在是太复杂了,除了针对上述情景的几种方案,还有一些不太合适的多态方案,例如:
- Variant +
Visitor:适合已知某几种类型,并且对每一种类型提供对应行为的简单情景。
- 策略模式:与
std::function
方案类似,但是使用模板类型参数,把行为封装在各种策略类中,没有运行时开销,但是这样的灵活性不太够,会产生太多类型,比较适合主要流程已经确定,只有几个步骤可修改的情景。