如何确保虚拟方法调用得到所有的方式传播到基类?(How to ensure that virtual

2019-08-01 03:44发布

用类层次结构一个非常常见的错误是指定在基类中的方法为虚,为了继承链中所有覆盖做了一些工作,而忘记传播到基本实现通话。

示例方案

class Container
{
public:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Nothing to do here
  }
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Set some property of pObject and pass on.
    Container::PrepareForInsertion(pObject);
  }
};

class MoreSpecializedContainer : public SpecializedContainer
{
protected:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Oops, forgot to propagate!
  }
};

我的问题是: 有没有好办法/图案确保基本实现总是会被调用在调用链的末端?

我知道的两种方法来做到这一点。

方法1

您可以使用一个成员变量作为一个标志,将其设置为基实现虚拟方法的正确值,并调用后检查其值。 这需要使用公共非虚方法,接口,为客户和使受保护的虚拟方法(实际上是做了一件好事),但它需要使用一个成员变量的专门用于此目的(这需要是可变的,如果虚拟方法必须是常数)。

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    m_callChainCorrect = false;
    PrepareForInsertionImpl(pObject);
    assert(m_callChainCorrect);
  }

protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    m_callChainCorrect = true;
  }

private:
  bool m_callChainCorrect;
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    // Do something and pass on
    Container::PrepareForInsertionImpl(pObject);
  }
};

方法2

另一种方式做到这一点是一个不透明的“cookie”参数来代替成员变量和做同样的事情:

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    bool callChainCorrect = false;
    PrepareForInsertionImpl(pObject, &callChainCorrect);
    assert(callChainCorrect);
  }

protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
  {
    *reinrepret_cast<bool*>(pCookie) = true;
  }
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
  {
    // Do something and pass on
    Container::PrepareForInsertionImpl(pObject, pCookie);
  }
};

这种方法不如在我看来,第一个,但它避免了使用专用的成员变量。

有什么其他的可能性有没有?

Answer 1:

你想出一些巧妙的方法来做到这一点,在(你确认)腹胀类并补充说,不解决对象的责任,但程序员deficiences代码的成本。

真正的答案是不是要在运行时做到这一点。 这是一个程序员的错误,而不是运行时错误。

这样做在编译时:使用一种语言构造,如果语言支持它,或者使用模式的强制执行它( 例如 ,模板方法),或让你的编译依赖于通过的测试,并成立了测试,以强制执行。

或者,如果没有传播导致派生类中失败了,让它失败,与通知他未能正确使用基类派生类的作者的异常信息。



Answer 2:

你要找的是简单的非虚拟接口模式。

它类似于你在这里做什么,但基类的实现可以保证被调用,因为它是唯一可以被称为实现。 它消除了,上面你的例子所需要的混乱。 并通过基类的调用是自动的,所以衍生版本不需要进行显式调用。

谷歌“非虚拟接口”的说明。

编辑 :查找“模板方法模式”后,我看到它对于非虚拟接口的另一个名字。 我从来没有听说过它的名字之前提到(我不完全GoF的歌迷俱乐部的正牌成员)。 就个人而言,我更喜欢名非虚接口,因为这个名字本身实际上描述的模式是什么。

再次编辑 :这里是这样做的NVI方式:

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    PrepareForInsertionImpl(pObject);

    // If you put some base class implementation code here, then you get
    // the same effect you'd get if the derived class called the base class
    // implementation when it's finished.
    //
    // You can also add implementation code in this function before the call
    // to PrepareForInsertionImpl, if you want.
  }

private:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject) = 0;
};

class SpecializedContainer : public Container
{
private:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    // Do something and return to the base class implementation.
  }
};


Answer 3:

如果只有一个级别继承您可以使用模板方法模式 ,其中公共接口是非虚拟和调用虚函数的实现。 那么基本的逻辑是其中放心地被称为公共职能。

如果你有继承不止一个级别,并希望每个类调用基类,那么你仍然可以使用模板方法模式,但与一捻, 使虚拟函数的返回值只能通过施工的 base ,从而derived将被强制调用基执行以返回一个值(在编译时执行)。

这并不强制每个类调用其直接基类,它可以跳过一个级别(我想不出来强制执行的好方法),但它确实迫使程序员有意识地决定,在它的工作原理换句话说针对疏忽没有恶意。

class base {
protected:
    class remember_to_call_base {
        friend base;
        remember_to_call_base() {} 
    };

    virtual remember_to_call_base do_foo()  { 
        /* do common stuff */ 
        return remember_to_call_base(); 
    }

    remember_to_call_base base_impl_not_needed() { 
        // allow opting out from calling base::do_foo (optional)
        return remember_to_call_base();
    }

public:
    void foo() {
        do_foo();
    }
};

class derived : public base  {

    remember_to_call_base do_foo()  { 
        /* do specific stuff */
        return base::do_foo(); 
    }
};

如果需要的public (非virtual )函数返回一个值的内virtual一个应返回std::pair< 返回型remember_to_call_base>


需要注意的事项:

  1. remember_to_call_base宣布私人所以只有它一个明确的构造函数friend (在这种情况下, base )可以创建这个类的一个新实例。
  2. remember_to_call_base 没有一个明确定义的拷贝构造函数所以编译器会创建一个与public可访问性。它可以根据值从它返回base执行。
  3. remember_to_call_base在申报protected的部分base ,如果这是在private部分derived将不能够引用它。


Answer 4:

完全不同的方法将注册函子。 派生类将寄存器一些功能(或成员函数)与所述基类,而在派生类的构造。 当实际功能是通过客户端调用它是一个基类函数,然后通过注册的功能迭代。 这扩展到继承的多层次,每一个派生类只需要关注其自身的功能。



Answer 5:

看看模板方法模式 。 (基本的想法是,你不必再调用基类的方法。)



Answer 6:

一条出路是根本不使用虚拟方法,而是允许用户注册回调,并做prepareForInsertion的工作之前调用这些。 这样,就不可能犯这种错误,因为它是基类,可以确保无论是回调和正常处理发生。 如果您希望此行为很多功能你可以有很多回调的结束。 如果你真的使用模式这么多,你可能想看看像AspectJ的工具(或任何C#相当于是),可以自动完成这样的事情。



Answer 7:

如果你发现你可以隐藏虚拟功能,使界面非虚,尝试而不是检查,如果其他用户没有打电话给你的功能只是自己调用它。 如果你的基本代码应在结束时调用,它应该是这样的:

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    PrepareForInsertionImpl(pObject);
    doBasePreparing(pObject);
  }

protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    // nothing to do
  }

private:
  void doBasePreparing(ObjectToInsert* pObject)
  {
    // put here your code from Container::PrepareForInsertionImpl
  }
};


文章来源: How to ensure that virtual method calls get propagated all the way to the base class?