上一篇 从引用传递到设计模式 (上) 的文末,提到非虚拟接口 NVI 的实现,即将虚函数声明为保护型或私有型,借由模板函数模式来实现 。

  园友 @KillU 看的很仔细,提出了一个问题:虚函数是 private 类型,继承可以么? 答案是:可以

5  实现权和调用权

  <Effective C++> 中给的解释是: 重写一个虚函数,指的是如何做事情 (how), 而调用一个虚函数,指的是什么时候做事情 (when)

  NVI 或 模板函数模式中,允许派生类重写虚函数,赋给派生类的是“实现权” - 即函数功能的实现;但是基类仍然掌握“调用权” - 即什么时候调用虚函数

  听起来很是拗口,来看一个实例,还是模板函数模式的实现,只不过是将虚函数声明为私有的 (private)

5.1  私有虚函数

  基类 AbstractClass 和 派生类 ConcreteClass

class AbstractClass {
public:
    void TemplateMethod();
private:
    virtual void PrimitiveOperation1();
    virtual void PrimitiveOperation2();
};

class ConcreteClass : public AbstractClass {
private:
    void PrimitiveOperation1() override;
    void PrimitiveOperation2() override;
};

  基类 AbstractClass 中,两个虚函数 PrimitiveOperation1,PrimitiveOperation2 以及 TemplateMethod 的实现

// implementation of private member function
void AbstractClass::PrimitiveOperation1() { std::cout << "Operation1 from AbstractClass !" << std::endl; }
void AbstractClass::PrimitiveOperation2() { std::cout << "Operation2 from AbstractClass !" << std::endl; }

// implementation of non-virtual member function
void AbstractClass::TemplateMethod() 
{ PrimitiveOperation1(); PrimitiveOperation2(); }

  派生类 ConcreteClass 中,重写两个虚函数 PrimitiveOperation1PrimitiveOperation2

// override
void ConcreteClass::PrimitiveOperation1() { std::cout << "Operation1 from ConcreteClass !" << std::endl; }
void ConcreteClass::PrimitiveOperation2() { std::cout << "Operation2 from ConcreteClass !" << std::endl; }

  当通过指针或引用调用虚函数时,具体是调用基类还是派生类里的虚函数,取决于动态绑定到该指针或引用的类对象

// call virtual functions in AbstractClass
AbstractClass* p1 = new AbstractClass;
p1->TemplateMethod();

// call virtual functions in ConcretetClass
AbstractClass* p2 = new ConcreteClass;
p2->TemplateMethod();

  输出的结果如下:

Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from ConcreteClass !
Operation2 from ConcreteClass !

  这个结果,乍一看并不会立即产生疑问,因为派生类重写了两个虚函数,这两个虚函数也是派生类自己的私有成员函数,调用自己类里的虚函数自然没什么问题。

5.2  虚函数不被重写

  下面改变一下程序,派生类 ConcreteClass 内不重写两个虚函数,只是单纯的继承自基类 AbstractClass,如下所示:

class ConcreteClass : public AbstractClass { };

  执行程序,输出结果:

Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from AbstractClass !
Operation2 from AbstractClass !

  这时候疑问便出现了:怎么派生类 ConcreteClass 居然可以访问基类 AbstractClass 的私有虚函数? 这可是私有类型

  其实这是一种错觉,并不是派生类直接调用了基类的私有虚函数,而是派生类的非虚成员函数 ConcreteClass::TemplateMethod ,

  因为继承自基类的非虚成员函数 AbstractClass::TemplateMethod,从而间接的调用了基类的私有虚函数。

  实际的“调用权”依然牢牢握在基类手中,只不过是基类提供了一个 TempaltMethod 的接口 (interface),可以让派生类来间接调用而已

5.3  私有非虚函数

  再次修改程序,基类内 PrimitiveOperation1 和 PrimitiveOperation2 不再声明为虚函数,也即基类从派生类手中收回了函数的“实现权”

class AbstractClass {
public:
    void TemplateMethod();
private:
    void PrimitiveOperation1(); // non-virtual
    void PrimitiveOperation2(); // non-virtual
};

  同时派生类 ConcreteClass 中,声明自己的私有成员函数 PrimitiveOperation1 和 PrimitiveOperation1,“隐藏”了基类中对应的同名函数

class ConcreteClass : public AbstractClass {
private:
    void PrimitiveOperation1();
    void PrimitiveOperation2();
};

  执行程序输出结果:

Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from AbstractClass !
Operation2 from AbstractClass !

  输出的结果和 5.2 是一样的,派生类调用 ConcreteClass::TemplateMethod,而 ConcreteClass::TemplateMethod 继承自 AbstractClass::TemplateMethod,

  因此,实际上,仍然还是基类内的非虚成员函数,调用基类内的私有成员函数

5.4  纯虚函数

  重申 5.1 中的一句话:当通过指针或引用调用虚函数时,具体调用基类还是派生类里的虚函数,取决于动态绑定到该指针或引用的类对象基类里的私有成员函数

  可以这么理解,虚函数涉及的是动态绑定,和它本身是公有、私有还是保护,并无多大关系

  实际中应用中,既然要使用模板方法模式,那就是说必定要在派生类 ConcreteClass 中重写 PrimitiveOperation1 和 PrimitiveOperation2

  而要保证这两个函数一定会被派生类重写,可以将它们声明为纯虚函数,即在函数最末尾加 “= 0”,如下所示:

class AbstractClass {
public:
    void TemplateMethod();
private:
    virtual void PrimitiveOperation1() = 0;
    virtual void PrimitiveOperation2() = 0;
};

  此时,因为基类内申明了纯虚函数,所有基类 AbstractClass 变成了一个抽象基类(abstarct base class)

  抽象基类只能提供接口 (interface),而不能实例化, 执行下面代码是会出错的:

AbstractClass* p1 = new AbstractClass;  // error !
p1->TemplateMethod();

 

小结:

1)  NVI gives the derived class control over how functionality is implemented, but the base class reserves the right when the function will be called

2)  Derived classes may redefine private inherited virtual functions

3)  Pure virtual functions specify inheritance of interface only

 

参考资料:

 <Effective C++> item 35

 <C++ Primer_5th> 15.4 Abstract Base Classes

 <Design Patterns> Template Method

 

posted on 2016-06-02 05:33  飞鸢逐浪  阅读(770)  评论(0编辑  收藏  举报