"Best fit" C++中插件的动态类型匹配
"Best fit" dynamic type matching for plugins in C++
我有一个几乎所有东西都是插件的体系结构。该架构是图形用户界面的基础,其中每个插件都由一个"表面"(即用户可以通过其与插件交互的UI控件)表示。这些曲面也是插件。每当添加一个新插件时,瘦主机就会自动确定哪个可用的表面最适合它。如何在C++中实现动态类型匹配就是这个问题的主题。
目前,该体系结构是用C#实现的,在很大程度上依赖于反射,正如您将看到的那样。然而,我现在正在为C++重新设计整个东西,但由于C++没有反射(而且我是C++的新手),我需要一些关于如何在没有它的情况下最好地复制这个功能的意见。
以下是目前在C#(简化和伪)中的操作方法:
所有插件都是Plugin
类的后代。
每个表面类型都标记有一个"目标插件类型",表示它是为插件设计的最特定/最深的子代。
最通用的曲面是只处理插件级别的曲面(例如,只显示名称标签)。这保证了所有插件都至少有一个能够在屏幕上表示它们的表面,即使它没有提供任何有用的交互方式。
让我们将这种类型称为PluginSurface
的"基线"曲面。如果——为了简洁起见(实际上是通过类属性完成的)——我们在曲面上使用一个属性来指示它们的目标类型,那么我们就有了
PluginSurface.TargetType = typeof(Plugin)
除非添加更多特定的曲面,否则所有插件都将被分配此通用曲面,无论它们在插件继承层次结构中的位置如何。
现在,假设我们添加了一个名为Father的新插件类型,它源于插件:
Father : Plugin
然后我们再添加一些,以创建一个小的示例层次结构:
Mother : Plugin
Daughter : Father
Son1 : Mother
Son2 : Mother
Grandson : Son1
此时,所有这些都将在屏幕上由通用PluginSurface
表示。因此,我们创建了额外的表面,以更具体地处理新插件的功能(所有表面都来自PluginSurface):
MotherSurface.TargetType = typeof(Mother)
GrandsonSurface.TargetType = typeof(Grandson)
由此,插件与表面的有效类型匹配应该是:
Father -> PluginSurface
Daughter -> PluginSurface
Mother -> MotherSurface
Son1 -> MotherSurface
Son2 -> MotherSurface
Grandson -> GrandsonSurface
好的,这就是我们期望得到的映射。从本质上讲,匹配是作为主机中的静态函数实现的,该函数采用插件类型并返回最佳拟合曲面的实例:
PluginSurface GetBestFitSurface(Type pluginType)
该函数遍历所有可用的曲面,并根据提供的pluginType
检查它们的TargetType
属性。更具体地说,它检查TargetTypeIsAssignableFrom
是否为pluginType。它创建了一个所有阳性匹配的列表。
然后,它需要将此候选列表缩小到最适合的。为此,我对可分配性进行了类似的检查,但这一次在所有候选人的TargetTypes之间,将每个候选人与其他候选人进行比较,并为每个候选人记录其TargetType可分配给其他候选人的TargetType中的多少个。可以将其TargetType分配(即强制转换)给大多数其他TargetTypes的候选人最适合。
后来发生的事情是,这个表面变成了有问题的插件的包装,在屏幕上排列自己,以反映它"理解"的插件的功能,这取决于配合的紧密程度。GrandsonSurface
是专门为表示GrandSon
而设计的,因此它将是该插件的完整UI。但Son1
和Son2
"只"得到了MotherSurface
,因此它们与Mother
不共同的任何功能都不会包含在它们的UI表示中(直到制作出更专业的Son
界面,此时将是最适合的)。请注意,这是预期用途-通常情况下,Mother插件将是抽象,而针对它的接口实际上是Son接口。因此,单个表面为一整类功能相似的插件提供了UI。
如果没有反射的支持,如何在C++中最好地做到这一点?
我想挑战是将C++模拟抽离到C#的Type.IsAssignableFrom(Type)
中,其中两种类型都是动态归因的。。我正在寻找dynamic_cast
和typeid
的方向,但到目前为止,我还没有完全掌握如何去做。任何关于架构、模式或实现细节的提示都将不胜感激。
编辑1:我认为我已经解决了这里的主要问题(见下面我自己的答案,其中包括一个[非常]粗略的C++实现),但可能还有更好的模式。随意拆开。
编辑2:在下面的一个问题中提出了一个改进的模型:C++中的"穷人的反思"(又名自下而上的反思)。
一个选项是在插件基类中有一个函数:
virtual std::string id() const { return "Plugin"; }
哪些派生类可以覆盖,例如Grandson知道它的基类是Mother,所以可能会编码:
override std::string id() const { return "Grandson " + Mother::id(); }
这样,当你在一个插件上调用id()
时,你会得到类似于"祖母插件"的东西。你可以把它抛出std::stringstream ss(id);
,我们抛出while (ss >> plugin_id)
,然后用plugin_id
作为表面容器中的键进行搜索。。。。
我认为您不会找到灵活使用typeid
或dynamic_cast
的方法,因为typeid
不会告诉您基类(*),dynamic_cast
必须知道它在编译时要测试的类型。我想你希望能够使用新的插件,并更改一些文件或其他配置的id->surface映射,而不需要编辑你的代码。。。。
(*)如果您准备成为非标准,并且它符合您的可移植性要求,C++:使用typeinfo测试类继承文档是在运行时获得基类typeinfo
的一种方法。
经过思考,我意识到以下内容应该有效:
由于表面类型应该已经用它们各自的"目标插件类型"进行标记,我意识到它们还不如在其中硬编码类型(因为一旦编译了表面插件,它就不会改变。无论如何,这基本上就是C#中类型标记的方式-尽管它是使用Attributes.Duh类完成的)。
这意味着,只要"可分配性"检查是在每个表面类内部进行的(内部知道它的目标类型),而不是在全局函数中进行的(需要同时告知检查的源和目标类型,因此出现了问题),它就变成了具有已知目标类型(即表面的目标插件类型)的常规强制转换操作。
我想相关的函数可以被模板化,这样模板类型T将代表他们的"目标插件类型",以及已知的转换目的地类型。
它可能有这样的功能:
bool TargetTypeIsAssignableFrom(Plugin* plugin)
并且该函数将返回常规CCD_ 26的结果(即
dynamic_cast<T>(plugin)
因此,当执行plugin->表面匹配时,不是根据全局函数中每个表面的TargetType检查pluginType(需要同时提供这两种类型),每个表面都会被简单地询问它们与有问题的插件的兼容性,它通过尝试对其已知T-的dynamic_cast进行测试,并且返回CCD_。
最后,对候选对象的TargetTypes进行相互检查,以确定最佳匹配。
这是一个快速而肮脏但功能性的过程说明(请原谅我的C++):
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Plugin {
protected:
string _name;
public:
Plugin() { _name = "Plugin"; };
virtual string Name() { return _name; }
};
class Plugin_A : public Plugin {
public:
Plugin_A() { _name = "Plugin_A"; };
};
class Plugin_B : public Plugin_A {
public:
Plugin_B() { _name = "Plugin_B"; };
};
class Plugin_C : public Plugin {
public:
Plugin_C() { _name = "Plugin_C"; };
};
Plugin * Global_Plugin = new Plugin;
Plugin_A * Global_Plugin_A = new Plugin_A;
Plugin_B * Global_Plugin_B = new Plugin_B;
class Surface {
protected:
string _name;
public:
Surface() { _name = "Surface"; };
string Name() { return _name; }
virtual Plugin* TargetType() { return Global_Plugin; }
virtual bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin*>(plugin) != nullptr; } // TargetType: Plugin
};
class Surface_A : public Surface {
public:
Surface_A() { _name = "Surface_A"; };
Plugin* TargetType() { return Global_Plugin_A; }
bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin_A*>(plugin) != nullptr; } // TargetType: Plugin_A
};
class Surface_B : public Surface_A {
public:
Surface_B() { _name = "Surface_B"; };
Plugin* TargetType() { return Global_Plugin_B; }
bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin_B*>(plugin) != nullptr; } // TargetType: Plugin_B
};
vector<Surface*> surfaces;
Surface * GetSurface(Plugin* plugin) {
vector<Surface*> candidates;
cout << "Candidate surfaces for " << plugin->Name() << ":" << endl;
for (auto i = begin(surfaces); i != end(surfaces); ++i) {
if ((*i)->CanHost(plugin)) {
cout << "t" << (*i)->Name() << endl;
candidates.push_back(*i);
}
}
int bestFit = 0, fit;
Surface * candidate = nullptr;
for (auto i = begin(candidates); i != end(candidates); ++i) {
fit = 0;
for (auto j = begin(candidates); j != end(candidates); ++j) {
if (j == i || !(*j)->CanHost((*i)->TargetType())) continue;
++fit;
}
if (candidate != nullptr && fit <= bestFit) continue;
bestFit = fit;
candidate = *i;
}
cout << "Best fit for " << plugin->Name() << ":" << endl;
cout << "t" << candidate->Name() << endl;
return candidate;
}
int main() {
Surface * s[3];
s[0] = new Surface;
s[1] = new Surface_A;
s[2] = new Surface_B;
for (int i = 0; i < 3; ++i) {
surfaces.push_back(s[i]);
}
Plugin * p[3];
p[0] = new Plugin_A;
p[1] = new Plugin_B;
p[2] = new Plugin_C;
for (int i = 0; i < 3; ++i) {
GetSurface(p[i]);
cout << endl;
}
for (int i = 0; i < 3; ++i) {
delete p[i];
delete s[i];
}
cin.get();
delete Global_Plugin;
delete Global_Plugin_A;
delete Global_Plugin_B;
return EXIT_SUCCESS;
}
由于您来自一种具有反射功能的语言,并且正在询问C++中的类似功能,因此我建议您应该研究一下";"模板元编程";以及";基于策略的设计"类型特征";从而将这些决策推到编译时间。
在您的情况下,我建议您适当地标记类,并在编译时使用std::is_same
、std::is_base_of
提供的元编程技术做出这些决定,std::is_convertible
、std::enable_if
和std::conditional
当你似乎正在转向一个重大的重构时,关于这个主题的更好、更深入的资源实际上是Alexandrescu、Alexandrescu的书、Di Gennaro的书和David Abrahams关于这些技术的书中的所有内容。
我特别发现这些帖子在所有这些事情上都很有见地。。。
HTH,因为TMP很有趣
- 非类类型表达式的静态类型与动态类型之间的差异
- 与基类子对象相关的表达式的动态类型
- 在C++中更改对象的动态类型
- 如何在没有RTTI的情况下设计动态类型系统
- 使用 LLVM IR 实现动态类型语言
- 如何理解"动态类型:〈prvalue〉静态类型的prvalue表达式PP_7
- 使用标准类型的动态类型信息实例化标识符
- 为什么动态类型直到C++运行时才知道
- 具有模板化类型的动态类型Singleton.这是可行的方法吗?[提供的解决方案]
- 如何为动态类型语言构建编译器
- 将供应序列化改为动态类型
- C 静态和动态类型铸造
- 如何在C++中获取动态类型名称
- "Best fit" C++中插件的动态类型匹配
- 引用对象的动态类型何时可以更改
- 常规/动态类型作为参数C/C++
- 对于动态类型为强制转换类型的对象,dynamic_cast失败
- malloc分配的对象动态类型是什么
- 动态类型定义
- 一种语言可以有编译时检查但具有动态类型的特性吗