“最佳配合” C ++中插件的动态类型匹配 [英] "Best fit" dynamic type matching for plugins in C++

查看:128
本文介绍了“最佳配合” C ++中插件的动态类型匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个架构,几乎一切都是一个插件。该架构基于图形用户界面,其中每个插件由表面(即,用户可以通过其与插件交互的UI控件)表示。这些表面也是插件。每当添加一个新的插件,瘦的主机自动确定哪些可用的表面是最好的UI匹配。如何使用C ++实现动态类型匹配是这个问题的主题。



目前,架构在C#中实现,主要依赖于反射,看看。但是,我现在正在为C ++重新设计整个事情,但由于C ++没有反射(因为我是C ++的新手),我需要一些输入,如何最好地复制这个功能没有它。



以下是目前在C#(简体和伪)中的操作:



所有插件都是 Plugin class。



每个表面类型都标记有一个'目标插件类型',表示插件的最具体/最深的后代它是为。设计的。



最通用的表面是只处理插件级别的表面(例如只显示名称标签)。这保证所有的插件将至少有一个表面能够在屏幕上表示它们,即使它不提供任何有用的交互手段。



让我们称之为基线表面类型 PluginSurface 。如果 - 为了简洁,这里(实际上它是通过类属性) - 我们使用表面上的属性来指示其目标类型,我们将有

  PluginSurface.TargetType = typeof(Plugin)



现在,我们添加一个名为Father的新插件类型,源自Plugin:

 父亲:插件

然后我们添加几个,创建一个示例层次结构:

 插件
女儿:父
Son1:母亲
Son2:母亲
Grandson:Son1

此时,所有这些都将由通用的 PluginSurface 在屏幕上表示。所以我们创建额外的表面来更具体地处理新插件的功能(所有表面都从PluginSurface下降):

  MotherSurface.TargetType = typeof(Mother)
GrandsonSurface.TargetType = typeof(Grandson)

插件到表面的有效类型匹配应该是:

 父亲 - PluginSurface 
女儿 - > PluginSurface
Mother - > MotherSurface
Son1 - > MotherSurface
Son2 - > MotherSurface
Grandson - > GrandsonSurface

好的,这是我们期望的映射。基本上,匹配被实现为主机中的静态函数,它接受插件类型并返回最佳适配表面的实例:

  PluginSurface GetBestFitSurface(type pluginType)

函数遍历所有可用表面, pluginType TargetType 属性。更具体地说,它检查TargetType IsAssignableFrom 是否为pluginType。它会创建一个包含所有正匹配的列表。



然后需要将此候选列表缩小到最佳匹配 。为此,我做一个类似的可转让性检查,但这一次在所有候选人的TargetTypes 之间,检查每一个对每个其他人,对于每个候选人,我记下了多少其他候选者的TargetType可分配给其TargetType。



后面会发生的是,这个表面成为了一个包装插件的插件。问题,安排在屏幕上反映该插件的理解的功能,取决于紧配合。 GrandsonSurface 专门用于表示 GrandSon ,因此它将是该插件的完整UI。但是 Son1 Son2 是只获得 MotherSurface ,因此它们与 Mother 不相同的任何功能都不会包含在它们的UI表示中(直到更专业的 Son 界面,此时将是最佳配合等)。注意,这是预期的用途 - 通常,Mother插件将是抽象,并且接口定位它将真正是一个子接口,意图包装母亲的所有儿童。因此,单个表面为一整类具有类似特征的插件提供了UI。



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



我想要的挑战是抽出C ++模拟C#的 Type.IsAssignableFrom(Type),其中两个类型都动态归因..我在 dynamic_cast typeid 的方向,但是到目前为止我还没有得到一个坚定的把握如何去做。



编辑1:我认为我已经解决了这里的主要问题(见下面我自己的回答,其中包括一个[非常]粗糙的C ++实现),但可能有更好的模式。随意剥开它。



编辑2:在此处提供的后续问题中提供了改进的模型:

>

在考虑后,我意识到以下应该工作:



由于表面类型已经被标记为各自的目标插件类型 ,我意识到他们可能有这种类型的硬编码在他们(因为它不会改变一旦表面插件编译,这基本上是如何类型标记在C#中完成 - 虽然



这意味着只要在每个表面类中进行'assignability'检查 (内部知道它所针对的是什么类型),而不是在一个全局函数(需要告知检查的源和目标类型,因此是问题),它变成具有已知目的地类型的常规转换操作即表面的目标插件类型)。



我想相关的函数可以模板化,所以模板类型T将代表他们的目标插件类型类型。



它可以有如下的函数:

  bool TargetTypeIsAssignableFrom(Plugin * plugin)

,函数将返回常规 dynamic_cast (即这样的转换是否成功):

  dynamic_cast<所以当插件 - >表面匹配执行时,而不是检查pluginType(插件)


$ b对于每个表面的全局函数中的TargetType(需要同时提供这两种类型),每个表面都被简单地查询它们与所讨论的插件的兼容性,它通过尝试dynamic_cast到它的已知 T - 并且返回 true 的那些被视为候选者。



'



这里是一个快速,肮脏 - 但功能的过程说明(原谅我的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
};

类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 *>表面;

Surface * GetSurface(Plugin * plugin){
vector< Surface *>候选人;

cout<< 用于<< plugin-> Name()<< :<< endl;

for(auto i = begin(surfaces); i!= end(surfaces); ++ i){
if((* i) - > CanHost
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<< 最适合<< 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;
}


I have an architecture where virtually everything is a plugin. This architecture underlies a graphic user interface where each plugin is represented by a "surface" (i.e. a UI control through which the user can interact with the plugin). These surfaces are also plugins. Whenever a new plugin is added, a thin host automatically determines which of the available surfaces is the best UI match for it. How that dynamic type matching can be accomplished in C++ is the subject of this question.

At present, the architecture is implemented in C#, relying heavily on reflection, as you'll see. However, I am now in the process of redesigning the whole thing for C++, but since C++ doesn't have reflection (and since I am new to C++) I need some input on how to best replicate this functionality without it.

Here's how it's currently done in C# (simplified & pseudo):

All plugins are descendants of a Plugin class.

Each surface type is tagged with a 'target plugin type', indicating the most specific / deepest descendant of Plugin it is designed for.

The most generic of the surfaces is one that just handles the level of Plugin (just showing a name label, for example). This guarantees that all plugins will have at least one surface capable of representing them on screen, even if it doesn't provide any useful means of interaction.

Let's call this "baseline" surface type for PluginSurface. If - for the sake of brevity here (in reality it's done via Class Attributes) - we use a property on surfaces to indicate their target type, we'd have

PluginSurface.TargetType = typeof(Plugin)

Unless more specific surfaces are added, all plugins would be assigned this generic surface, no matter where they are in the Plugin inheritance hierarchy.

Now, let's say we add a new plugin type called Father, deriving from Plugin:

Father : Plugin

Then we add a few more, to create a little sample hierarchy:

Mother : Plugin
Daughter : Father
Son1 : Mother
Son2 : Mother
Grandson : Son1

At this point, all of these would be represented on screen by the generic PluginSurface. So we create additional surfaces to more specifically handle the functionality of the new plugins (all surfaces descend from PluginSurface):

MotherSurface.TargetType = typeof(Mother)
GrandsonSurface.TargetType = typeof(Grandson)

From this, the effective type matching of plugin-to-surface should be:

Father -> PluginSurface
Daughter -> PluginSurface
Mother -> MotherSurface
Son1 -> MotherSurface
Son2 -> MotherSurface
Grandson -> GrandsonSurface

Ok, so this is the mapping we expect to end up with. Essentially, the matching is implemented as a static function in the host that takes the plugin type and returns an instance of the best-fit-surface:

PluginSurface GetBestFitSurface(Type pluginType)

The function iterates through all the available surfaces and checks their TargetType property against the supplied pluginType. More specifically, it checks whether the TargetType IsAssignableFrom the pluginType. It creates a list of all the positive matches.

It then needs to narrow down this list of candidates to the best fit. For that I do a similar check of assignability, but this time between all the candidates' TargetTypes, checking each one against each of the others, and for each candidate I make a note of how many of the other candidates' TargetTypes its TargetType is assignable to. The candidate whose TargetType can be assigned (i.e. cast) to the most other TargetTypes is the best fit.

What happens later is that this surface becomes a wrapper around the plugin in question, arranging itself on screen to reflect the features of that plugin it "understands", depending of the tightness of the fit. A GrandsonSurface is specifically designed to represent GrandSon, so it will be a complete UI for that plugin. But Son1 and Son2 are "only" getting a MotherSurface, so any features they don't have in common with Mother will not be included in their UI representation (until a more specialized Son interface is made, at which point that will be the best fit etc). Note that this the intended use - normally, the Mother plugin would be abstract and the interface targeting it would really be a Son interface, intended to wrap all of Mother's children. Thus a single surface provides the UI for a whole class of similarly featured plugins.

How can this best be done in C++ without the support of reflection?

I suppose the challenge is to smoke out the C++ analog to C#'s Type.IsAssignableFrom(Type) where both Types are dynamically attributed.. I'm looking in the direction of dynamic_cast and typeid, but so far I haven't gotten a firm grasp on how to go about it. Any tips on architecture, pattern or implementation details would be greatly appreciated.

Edit 1: I think I have solved the main problem here (see my own answer below, which includes a [very] rough C++ implementation), but there might be better patterns. Feel free to rip it apart.

Edit 2: An improved model is presented in a follow-up question found here: "Poor Man's Reflection" (AKA bottom-up reflection) in C++.

解决方案

After thinking about it, I realized that the following should work:

Since the surface types are already supposed to be tagged with their respective 'target plugin type', I realized that they might as well have that type hard coded in them (as it's not going to change once a surface plugin is compiled. This is basically how the type tagging is done in C# anyway - although there it is done using class Attributes. Duh).

This means that as long as the 'assignability' checking is done inside each surface class (that internally knows what type it is targeting) rather than in a global function (that needs to be told both the source and the target type of the check, hence the problem), it becomes a regular cast operation with a known destination type (i.e. the surface's target plugin type).

I guess the relevant function could be templated, so that the template type T would represent their 'target plugin type' and also the known destination type of the cast.

It could have a function like so:

bool TargetTypeIsAssignableFrom(Plugin* plugin)

and the function would return the result of a regular dynamic_cast (i.e. whether such a cast is successful or not):

dynamic_cast<T>(plugin)

So when the plugin -> surface matching is performed, rather than checking the pluginType against each surface's TargetType in a global function (that would need to be fed both types), each surface is simply queried about their compatibility with the plugin in question, which it tests by attempting a dynamic_cast to its known T - and the ones that return true are considered candidates.

Finally the candidates' TargetTypes are checked against each other to determine the best fit.

Here's a quick and dirty - but functional - illustration of the process (pardon my 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 ++中插件的动态类型匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆