这种“方法注入”的虚拟继承使用模式是一种已知的范例吗?
Posted
技术标签:
【中文标题】这种“方法注入”的虚拟继承使用模式是一种已知的范例吗?【英文标题】:Is this use pattern of virtual inheritance for "method injection" a known paradigm? 【发布时间】:2014-06-20 02:45:48 【问题描述】:昨天,我遇到了这个问题:forcing unqualified names to be dependent values 最初,这似乎是一个与损坏的 VC++ 行为有关的非常具体的问题,但是在尝试解决它时,我偶然发现了一个我没有来的虚拟继承的使用模式之前(在告诉你我的问题后,我会在一秒钟内解释它)。我发现它很有趣,所以我在 SO 和 google 上寻找它,但我找不到任何东西。也许,我只是不知道它的正确名称(“方法注入”是我的猜测之一),它实际上是广为人知的。这也是我向社区提出的问题的一部分:这是一种常见的使用模式还是另一种已知范式的特例?您是否发现任何可以通过其他解决方案避免的问题/陷阱?
这个模式可以解决的问题如下:假设你有一个类Morph
和一个方法doWork()
。在doWork()
中,调用了几个函数,这些函数的实现应该由用户选择(这就是该类被称为Morph
的原因)。让我们称这些函数为变色龙(因为 Morph 类不知道它们最终会是什么)。实现这一点的一种方法当然是创建 Morph 类的 chameleons 虚拟方法,因此用户可以从 Morph
派生并覆盖选定的方法。但是,如果期望用户使用不同的组合来为不同的变色龙选择实现呢?然后,对于每个组合,必须定义一个新类。此外,如果有多个类似Morph
的类,其中相同的功能应该是变色龙怎么办?用户如何重用她已经实现的替换?
对于“必须定义多个类”的问题,模板立即跃入脑海。用户不能通过将类作为定义所需实现的模板参数传递来选择他想要的变色龙实现吗? IE。类似Morph<ReplaceAB>
的东西应该有效地替换doWork()
中的doWork()
中的变色龙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()
和@987654340 @ 只是取自 Morph
继承自的任何东西?这实际上是不可能的,因为对变色龙的调用不依赖于模板参数,并且在依赖的继承类中不会查找此类非依赖名称(如果需要,请尝试)。这意味着,我们必须以某种方式实现变色龙调用绑定到稍后可以被所需实现替换的东西。
这就是虚拟继承的用武之地:通过让Morph
从Chameleons
继承(不依赖于模板参数),doWork()
中的非限定变色龙调用绑定到Chameleons
中的虚函数。因为Morph
和Replacement
类实际上继承自Chameleons
,所以任何Morph
对象中只会有一个Chameleons
对象,并且虚函数调用将在运行时调度 /em> 到最派生类中的实现,我们通过模板继承“走私”。所以,虽然doWork()
中不合格的变色龙名字在编译时无法解析为想要的实现(按照标准),但它们仍然可以通过虚拟基类进行一层间接调用。好笑吧? (除非你告诉我这更容易以不同的方式完成,或者这种模式广为人知。)
【问题讨论】:
这是关于 SO 最长的问题之一(我希望) 是的,对不起,我很想详细解释一下使用模式。问题本身实际上并没有那么长;-) 好吧,也许我不太明白,这比仅仅使用一堆仿函数有什么好处? 我不太明白如何在不为包含函子的结构实现“setReplacement”方法的情况下使用函子做到这一点,这将接近我发布的原始解决方案here。使用上述解决方案,编译器会为您完成所有工作,您可以根据需要方便地捆绑替换。基本上,您可以在任意函数变色龙中进行函数调用,只需用 Morph 类包装即可。 所以想法是给定一个不相关的函数void f(...) /*code*/ A();
,您可以将f
中的代码c/p 到Morph<...>::doWork(...) /* code copied from f */ A();
并方便地用任意函数调用替换对A()
的调用不更改您从f()
复制的代码?
【参考方案1】:
您的解决方案运行良好。虚拟继承避免了歧义错误。可变参数模板带来优雅的实例化语法。而不是这样的:
class M1 : public ReplaceAB, ReplaceC i1;
i1.doWork();
你只有一行:
Morph<ReplaceAB, ReplaceC>().doWork();
在我看来,建议的模式并不常见。同时,这真的是新事物吗?嗯,有数百万行代码......有人使用类似东西的机会根本不为零。很可能你永远不会知道这一点。
【讨论】:
以上是关于这种“方法注入”的虚拟继承使用模式是一种已知的范例吗?的主要内容,如果未能解决你的问题,请参考以下文章