条款25:将 constructor和 non-member function 虚化
1️⃣ Virtual constructor
virtual constructor听起来很荒谬,但是它们很有用。
2️⃣ 案例引入
1 | class NLComponent { // 抽象基类,用于时事消息 |
classes 彼此之间的关系

目的
NewsLetter对象尚未开始运作的时候,可能存储于磁盘中。- 为了能根据磁盘上的数据产出一份
Newsletter,如果我们让NewsLetter拥有constructor并用istream作为自变量,会很方便。 - 这个
constructor将从stream读取数据以便产生必要的list<NLComponent>:
1 | class NewsLetter { |
或者,如果将棘手的东西搬移到另一个名为readcomponent的函数,就变成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NewsLetter {
public:
...
private:
//从str读取下一个 NLComponent 的数据,
//产生组件(component),并返回一个指针指向它。
static NLComponent * readComponent(istream& str);
...
};
NewsLetter::NewsLetter(istream& str)
{
while (str) {
//将 readComponent 返回的指针加到 componentslist 尾端
components.push_back(readComponent(str));
}
}
- 🥵
readComponent做了什么事情?readComponent产生一个崭新对象,或许是个TextBlock,或许是个Graphic,视读入的数据而定。这些都是NLComponent的子类。- 由于
readComponent产生新对象,所以行为仿若constructor,但它能够产生不同类型的对象,所以我们称它为一个virtual constructor。 - 所谓
virtual constructor是某种函数,视其获得的输入,可产生不同类型的对象。
3️⃣ Virtual copy constructor
定义
Virtual copy constructor会返回一个指针,指向其调用者(对象)的一个新副本。
实现方式
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
27class NLComponent
{
public:
// 声明 virtual copy constructor
virtual NLComponent* clone () const = 0;
...
};
class TextBlock: public NLComponent
{
public:
virtual TextBlock* clone() const // virtual copy constructor
{
return new TextBlock(*this);
}
...
};
class Graphic: public NLComponent
{
public:
virtual Graphic* clone() const // virtual copy constructor
{
return new Graphic(*this);
}
...
};- 解析
class的virtual copy constructor调用实际的copy constructor。- “
copy”这层意义对这两个函数而言是一样的。- 如果真正的
copy constructor执行的是浅复制,virtual copy constructor也一样。 - 如果真正的
copy constructor执行的是深复制,virtual copy constructor也一样 - 如果真正的
copy constructor做了某些煞费苦心的动作,如reference counting(引用计数)或copy-on-write(写时复制),virtualcopyconstructor也一样。
- 如果真正的
- 原理
- 当
derived class重新定义其base class的一个虚函数时,不一定需要声明与原本类型相同的返回类型。 - 如果虚函数的返回类型是指针(或
<font style="background-color:#FBDE28;">reference</font>)指向一个<font style="background-color:#FBDE28;">base class</font>,则<font style="background-color:#FBDE28;">derived class</font>的函数可以返回一个指针(或<font style="background-color:#FBDE28;">reference</font>),指向该<font style="background-color:#FBDE28;">base class</font>的一个<font style="background-color:#FBDE28;">derived class</font>。
- 当
- 解析
实现_ NewsLetter _的 copy constructor
1 | class NewsLetter { |
4️⃣ 将 Non-Member Functions 的行为虚化
就像
constructors无法真正被虚化一样,non-member functions也是。然而就像我们认为应该能够以某个函数构造出不同类型的新对象一样,我们也认为应该可以让
non-member functions的行为视其参数的动态类型而不同。举个例子,假设你希望为
TextBlock``和Graphic实现出output操作符,明显的办法就是让output操作符虚化。然而,output操作符(operator<<)获得一个ostream&作为其左端自变量,因此它不可能成为TextBlock或Graphic class的一个member 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
29class NLComponent {
public:
// output operator的非传统声明。
virtual ostream& operator<<(ostream& str) const = 0;
...
};
class TextBlock: public NLComponent {
public:
//virtualoutput operator(也是打破传统)。
virtual ostream& operator<<(ostream& str) const;
};
class Graphic: public NLComponent {
public:
//virtualoutput operator(也是打破传统)。
virtual ostream& operator<<(ostream& str) const;
};
TextBlock t;
Graphic g;
...
t << cout; // 通过 virtual operator<<,在 cout 身上
//打印出 t。注意此语法与传统不符。
g << cout; // 通过virtual operator<< 在 cout 身上
//打印出g。注意此语法与传统不符。
5️⃣ 正确做法
同时定义
operator<<和print,并令前者调用后者。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class NLComponent
public:
virtual ostream& print(ostream& s) const = 0;
};
class TextBlock: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
inline
ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}non-memberfunction的虚化十分容易- 写一个虚函数做实际工作,再写一个什么都不做的非虚函数,只负责调用虚函数。
- 当然啦,为了避免此巧妙安排蒙受函数调用所带来的成本,你可以将非虚函数
inline化。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!
