这种使用“方法注入”的虚拟继承模式是一种已知的范例吗?

时间:2014-06-20 02:45:48

标签: c++ inheritance virtual-inheritance

昨天,我遇到了这个问题:forcing unqualified names to be dependent values最初,这似乎是一个与破坏VC ++行为有关的非常具体的问题,但在尝试解决它时,我偶然发现了虚拟继承的使用模式。之前遇到过(在我告诉你问题之后,我会在一秒钟内解释)。我发现它很有趣,所以我在SO和google上寻找它,但我找不到任何东西。也许,我只是不知道它的正确名称(“方法注入”是我的猜测之一),它实际上是众所周知的。这也是我对社区的问题的一部分:这是一种常见的使用模式还是另一种已知范例的特例?您是否看到了其他解决方案可以避免的任何问题/陷阱?

此模式可以解决的问题如下:假设您有一个方法Morph的类doWork()。在doWork()内,调用了几个函数,其实现应该由用户选择(这就是为什么类被称为Morph)。让我们称之为函数变色龙(因为Morph类最终不知道它们将是什么)。实现此目的的一种方法当然是使变色龙成为Morph类的虚拟方法,因此用户可以从Morph派生并覆盖所选方法。但是,如果期望用户使用不同的组合来选择不同变色龙的实现,该怎么办呢?然后,对于每个组合,必须定义新类。另外,如果有多个Morph类似的类,其中相同的函数应该是变色龙呢?用户如何重用已经实现的替换?

至于“必须定义多个类”的问题,立即模板跳进了一个人的脑海。用户不能通过将类作为定义所需实现的模板参数来选择他想要的变色龙实现吗?即像Morph<ReplaceAB>这样的东西应该有效地替换A()中的变色龙B()doWork()以及一些实现,留下可能的其他变色龙,例如C(),不受影响。使用C ++ 11和可变参数模板,即使组合也不会成为问题:Morph<ReplaceAB, ReplaceC, WhateverMore...>嗯,确切地说,这个模式可以做什么(见下面的解释):

#include <iostream>

using namespace std;

// list all chameleons, could also be make some of them
// pure virtual, so a custom implementation is *required*.
struct Chameleons
{
  virtual void A() { cout << "Default A" << endl; }
  virtual void B() { cout << "Default B" << endl; }
  virtual void C() { cout << "Default C" << endl; }
};

// Chameleon implementations for A and B
struct ReplaceAB : virtual Chameleons
{
  virtual void A() { cout << "Alternative A" << endl; }
  virtual void B() { cout << "Alternative B" << endl; }
};

// Chameleon implementation for C
struct ReplaceC : virtual Chameleons
{
  virtual void C() { cout << "Alternative C" << endl; }
};

// A(), B(), C() in this class are made chameleons just by
// inheriting virtually from Chameleons
template <typename... Replace>
struct Morph : virtual Chameleons, Replace...
{
  void doWork() {
    A();
    B();
    C();
    cout << endl;
  }
};

int main()
{
  //default implementations
  Morph<>().doWork();
  //replace A and B
  Morph<ReplaceAB>().doWork();
  //replace C
  Morph<ReplaceC>().doWork();
  //replace A, B and C;
  Morph<ReplaceAB,ReplaceC>().doWork();
}

其输出如下:

Default A
Default B
Default C

Alternative A
Alternative B
Default C

Default A
Default B
Alternative C

Alternative A
Alternative B
Alternative C

仅看到这个工作解决方案,上述想法的问题实际上并不那么明显:无法Morph只能从指定为模板参数的类派生,因此变色龙A(),{{ 1}}和B()只取自C()继承的内容?这实际上是不可能的,因为对变色龙的调用不依赖于模板参数,并且在依赖的继承类中不会查找此类非依赖名称(如果需要,请尝试使用它)。这意味着,我们必须以某种方式实现变色龙调用绑定到以后可以被所需实现替换的东西。

这就是虚拟继承的来源:通过让Morph继承自Morph(不依赖于模板参数),Chameleons中的非限定变色龙调用绑定到虚函数在doWork()。由于ChameleonsMorph类几乎从Replacement继承,因此在任何Chameleons对象中只会有一个Chameleons对象,并且虚函数调用将将在运行时分派给最派生类中的实现,我们通过模板化继承“偷偷进入”。因此,虽然Morph中的不合格变色龙名称无法在编译时解析为所需的实现(根据标准),但它们仍然可以通过虚拟基类的间接层调用。好笑,对吧? (除非你告诉我这样做更容易,或者模式众所周知。)

1 个答案:

答案 0 :(得分:1)

您的解决方案正常。虚拟继承避免了歧义错误。 Variadic模板带来优雅的实例化语法。而不是像:

 class M1 : public ReplaceAB, ReplaceC {} i1;
 i1.doWork();

你只有一行:

Morph<ReplaceAB, ReplaceC>().doWork();

从我的观点来看,提议的模式并不常见。同时,这件事真的很新吗?好吧,有数百万行代码......有人使用类似东西的机会并非零。很可能你永远不会知道这一点。