1️⃣ Virtual constructor

  • virtual constructor听起来很荒谬,但是它们很有用。

2️⃣ 案例引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class NLComponent {				//	抽象基类,用于时事消息
public: // 的组件(components),
... // 其中内含至少一个纯虚函数。
};

class TextBlock: public NLComponent {
public:
... // 没有内含任何纯虚函数。
};

class Graphic: public NLComponent {
public:
... // 没有内含任何纯虚函数。
};

class NewsLetter { //一份时事通信是由一系列的
public: // NLComponent 对象构成的。
...
private:
list<NLComponent*> components;
};
  • classes 彼此之间的关系

  • 目的
    • NewsLetter对象尚未开始运作的时候,可能存储于磁盘中。
    • 为了能根据磁盘上的数据产出一份Newsletter,如果我们让NewsLetter拥有constructor并用istream作为自变量,会很方便。
    • 这个constructor将从stream读取数据以便产生必要的list<NLComponent>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class NewsLetter {
public:
NewsLetter(istream& str);
...
};

// 此 constructor 的伪代码(pseudo code)可能看起来像这样:
NewsLetter::NewsLetter(istream& str)
{
while (str) {
read the next component object from str;

add the object to the list of this
newsletter's components;
}
}

或者,如果将棘手的东西搬移到另一个名为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
    27
    class 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);
    }
    ...
    };
    • 解析
      • classvirtual 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs); // copy constructor
...
private:
list<NLComponent*> components;
};

/* 保证对象元素的正确性,不会发生类型偏移 */
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
for (list<NLComponent*>::const_iterator it = rhs.components.begin() ;
it != rhs.components.end();
++it)
{
components.push_back((*it)->clone ());
}
}

4️⃣ 将 Non-Member Functions 的行为虚化

  • 就像constructors无法真正被虚化一样,non-member functions也是。

  • 然而就像我们认为应该能够以某个函数构造出不同类型的新对象一样,我们也认为应该可以让non-member functions的行为视其参数的动态类型而不同

  • 举个例子,假设你希望为TextBlock``和Graphic实现出output操作符,明显的办法就是让output操作符虚化。然而,output操作符(operator<<)获得一个ostream&作为其左端自变量,因此它不可能成为TextBlockGraphic 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
    29
    class 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
    22
    class 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化。