"Best fit" C++中插件的动态类型匹配

"Best fit" dynamic type matching for plugins in C++

本文关键字:动态 类型 插件 Best fit C++      更新时间:2023-10-16

我有一个几乎所有东西都是插件的体系结构。该架构是图形用户界面的基础,其中每个插件都由一个"表面"(即用户可以通过其与插件交互的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。但Son1Son2"只"得到了MotherSurface,因此它们与Mother不共同的任何功能都不会包含在它们的UI表示中(直到制作出更专业的Son界面,此时将是最适合的)。请注意,这是预期用途-通常情况下,Mother插件将是抽象,而针对它的接口实际上是Son接口。因此,单个表面为一整类功能相似的插件提供了UI。

如果没有反射的支持,如何在C++中最好地做到这一点?

我想挑战是将C++模拟抽离到C#的Type.IsAssignableFrom(Type)中,其中两种类型都是动态归因的。。我正在寻找dynamic_casttypeid的方向,但到目前为止,我还没有完全掌握如何去做。任何关于架构、模式或实现细节的提示都将不胜感激。

编辑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作为表面容器中的键进行搜索。。。。

我认为您不会找到灵活使用typeiddynamic_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_samestd::is_base_of提供的元编程技术做出这些决定,std::is_convertiblestd::enable_ifstd::conditional

当你似乎正在转向一个重大的重构时,关于这个主题的更好、更深入的资源实际上是Alexandrescu、Alexandrescu的书、Di Gennaro的书和David Abrahams关于这些技术的书中的所有内容。

我特别发现这些帖子在所有这些事情上都很有见地。。。

HTH,因为TMP很有趣