这种虚拟继承的使用模式是否"method injection"已知范式?

Is this use pattern of virtual inheritance for "method injection" a known paradigm?

本文关键字:injection method 范式 是否 模式 虚拟 继承      更新时间:2023-10-16

昨天,我遇到了这个问题:强制非限定名称为依赖值最初,这似乎是一个非常具体的问题,与vc++行为有关,但在试图解决它时,我偶然发现了一个我以前没有遇到过的虚拟继承的使用模式(在告诉你我的问题之后,我将在第二秒解释它)。我发现它很有趣,所以我在so和谷歌上找了一下,但我找不到任何东西。也许,我只是不知道它的正确名称("方法注入"是我的猜测之一),它实际上是众所周知的。这也是我向社区提出的问题的一部分:这是一种常见的使用模式还是另一种已知范式的特殊情况?你是否看到了可以通过不同的解决方案来避免的问题/陷阱?

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

对于"必须定义多个类"的问题,模板立即跃入人们的脑海。用户不能选择变色龙实现他想通过传递类作为模板参数定义所需的实现?例如,像Morph<ReplaceAB>这样的东西应该用一些实现有效地取代doWork()中的变色龙A()B(),而不影响其他可能的变色龙,例如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(), B()C()只是从Morph继承的任何东西中获取?这实际上是不可能的,因为对变色龙的调用不依赖于模板参数,并且这些非依赖的名称不会在依赖的继承类中查找(如果您愿意,可以尝试一下)。这意味着,我们必须以某种方式实现变色龙调用绑定到稍后可以被所需实现替换的东西。

这就是虚继承的由来:通过让MorphChameleons继承(不依赖于模板参数),doWork()中的不合格变色龙调用绑定到Chameleons中的虚函数。因为MorphReplacement类虚拟地继承了Chameleons,所以在任何Morph对象中只有一个Chameleons对象,并且虚拟函数调用将在运行时分配给最派生类的实现,我们通过模板继承"走私"进来。因此,尽管doWork()中不合格的变色龙名称不能在编译时解析为所需的实现(根据标准),但它们仍然可以通过虚拟基类由间接层调用。有趣的,是吗?(除非你告诉我用另一种方式更容易做到这一点,或者该模式广为人知。)

您的解决方案运行良好。虚拟继承避免了歧义错误。可变模板带来了优雅的实例化语法。而不是像这样:

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

你只有一行:

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

在我看来,建议的模式是不常见的。同时,这真的是新东西吗?嗯,有数百万行代码……有人使用类似武器的可能性不是零。很可能你永远不会知道这一点。