虽然C++没有直接提供interface,但是却提供了虚函数、模板类型等各种语法,使得我们可以用各种方式实现多态,这里我们不区分动态多态和静态多态,而是从设计一个框架的角度,分别使用四种方案实现:

  • 虚函数(最简单直接的方式)
  • std::function
  • CRTP(最晦涩的方式)
  • deducing this(可以视作CRTP的简化,不再需要将派生类作为模板参数传递,要求C++23)

需求

我们考虑这样一个需求:

  • 基类A包括:(不可实例化)
    • 主方法run:调用func1,func2和func3
    • 实现方法func1
    • 实现方法func2(多态,允许子类修改)
    • 实现方法func3(多态,子类必须实现)
  • 派生类B1:继承A
    • 实现方法func3(多态,允许子类修改)
  • 派生类B2:继承A
    • 实现方法func3(多态,允许子类修改):调用func4
    • 实现方法func4(多态,允许子类修改)
  • 具体类C1:继承B2
    • 实现方法func4
  • 具体类C2:继承B2
    • 实现方法func3

最终我们直接通过各种对象自身来调用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; // 没有实现func3则编译报错
};

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(); // 没有实现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() {
// 基类A和中间基类B2不可以实例化

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(); // 没有实现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

因此:

  • 对计算性能敏感时,优先考虑 CRTPdeducing this
  • 更关注代码的灵活性和可读性,优先考虑虚函数或std::function

由于编译器优化的存在,这几种方案的运行时效率差异也与实际情景有关。

补充

C++实在是太复杂了,除了针对上述情景的几种方案,还有一些不太合适的多态方案,例如:

  • Variant + Visitor:适合已知某几种类型,并且对每一种类型提供对应行为的简单情景。
  • 策略模式:与std::function方案类似,但是使用模板类型参数,把行为封装在各种策略类中,没有运行时开销,但是这样的灵活性不太够,会产生太多类型,比较适合主要流程已经确定,只有几个步骤可修改的情景。