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

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

问题描述

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



目前,架构在C#中实现,依赖于反思,会看到然而,我现在正在重新设计C ++的整个过程,但是由于C ++没有反思(因为我是C ++的新手),我需要一些关于如何最好地复制此功能的输入。



以下是C#(简化和伪)中目前的工作原理:



所有插件都是<$ c的后代$ c> Plugin class。



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



最通用的表面只是处理Plugin级别(例如显示名称标签)。即使没有提供任何有用的互动方式,所有这些插件都将至少有一个表面能够在屏幕上进行表示。



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

  PluginSurface.TargetType = typeof(Plugin)

除非更具体的表面添加,所有插件将被分配这个通用表面,无论它们在插件继承层次结构中的位置。



现在,我们添加一个名为Father的新插件,从Plugin导出:

 父亲:插件

然后我们再添加一些,以创建一个小样本层次结构:

 母亲:插件
女儿:父亲
Son1:母亲
Son2:母亲
孙子:Son1

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

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

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

 父亲 - > PluginSurface 
女儿 - > PluginSurface
母亲 - > MotherSurface
Son1 - > MotherSurface
Son2 - > MotherSurface
孙子 - > GrandsonSurface

好的,所以这是我们期望的映射。基本上,匹配在主机中实现为静态函数,该函数采用插件类型并返回最佳拟合曲面的实例:

  PluginSurface GetBestFitSurface(Type pluginType)

该函数遍历所有可用的表面并检查 TargetType 属性提供的 pluginType 。更具体地说,它检查是否有TypeType IsAssignableFrom pluginType。它创建了所有正面比赛的列表。



然后需要将这个候选人列表缩小为最适合。为此,我做了类似的可分配性检查,但这次候选人的TargetTypes 之间的这一次,检查每一个对每个人,每个候选人我记下其他几个候选人的TargetTypes其TargetType可分配给。 TargetType可以分配给其他TargetTypes的候选人是最合适的。



稍后会发生什么,这个表面会变成围绕插件的包装问题,安排在屏幕上,以反映该插件的功能,它理解,取决于适合性的紧密性。 GrandsonSurface 专门用于表示 GrandSon ,因此它将是该插件的完整UI。但是 Son1 Son2 是只获得一个 MotherSurface ,所以他们与母亲不相同的任何功能将不会包含在他们的UI表示中(直到更专门的 Son 接口,此时 将是最合适的等等)。请注意,这是预期的用途 - 通常情况下,母亲插件将是抽象,并且面向目标的界面将真正是一个子界面,旨在将所有母亲的的。因此,单个表面为整个类别的类似功能的插件提供UI。



如果没有反思的支持,C ++中如何最好地完成这个UI?



我想,挑战是将C ++模拟抽烟到C#的 Type.IsAssignableFrom(Type),其中两种类型都是动态归因的。我正在朝 dynamic_cast typeid 的方向发展,但到目前为止,我还没有牢牢把握怎么去呢编辑1:我想我已经解决了这里的主要问题(参见下面我自己的答案,其中包括一个[非常]粗糙的C ++实现),但可能会有更好的模式。随意撕开它。



编辑2:在这里发现的后续问题中提供了一个改进的模型:可怜的人的反思(AKA自下而上的反射)在C ++中

解决方案

在考虑之后,我意识到以下内容应该有效:



由于表面类型已经被标记为各自的目标插件类型 ,我意识到他们也可能在他们的硬编码类型(因为一旦一个表面插件被编译就不会改变,这基本上是如何在C#中完成类型标记 - 尽管那么就可以使用class Attributes来完成了。



这意味着只要在每个表格类中执行'可分配性'检查 (内部知道它是什么类型的目标),而不是在一个全局功能(需要被告知来源和检查的目标类型,因此是问题),它变成具有已知目的地类型(即,表面的目标插件类型)。



我猜相关的功能可以模板化,所以模板类型T将表示他们的目标插件类型,也是已知的目标



它可以有一个这样的功能:

  bool TargetTypeIsAssignableFrom(Plugin * plugin)

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

  dynamic_cast< T>(插件)

所以当插件 - >表面匹配执行时,而不是检查pluginType针对每个表面的全局函数TargetType(需要同时提供这两种类型),每个表面只是简单地询问它们与所讨论的插件的兼容性,它通过尝试将dynamic_cast测试到其已知 T - 返回 true 被认为是候选人。



最后候选人的TargetTypes相互对照,以确定最合适。



这是一个快速而肮脏但功能齐全的过程说明(赦免我的C ++):

  #include<的iostream> 
#include< string>
#include< vector>

使用namespace std;

class Plugin {
protected:
string _name;
public:
Plugin(){_name =Plugin; };
虚拟字符串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; };
};

插件* Global_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; };
插件* 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(plugin)){
cout<<< \t<< (* i) - > Name()<< ENDL;
candidates.push_back(* i);
}
}

int bestFit = 0,fit;
Surface * candidate = nullptr; (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()))继续;
++ fit;
}
if(candidate!= nullptr&& fit< = bestFit)继续;
bestFit = fit;
candidate = * i;
}

cout<<< 最适合<< plugin-> Name()<<< :<< ENDL;
cout<<< \t<<候选人 - > Name()<< ENDL;

返回候选人;
}

int main(){
Surface * s [3];
s [0] = new Surface;
s [1] = new Surface_A;
s [2] = new Surface_B; (int i = 0; i <3; ++ i){
surfaces.push_back(s [i]);


}

插件* p [3];
p [0] = new Plugin_A;
p [1] = new Plugin_B;
p [2] = new Plugin_C; (int i = 0; i <3; ++ i){
GetSurface(p [i]);


cout<<< ENDL; (int i = 0; i< 3; ++ i){
delete p [i];
}


delete s [i];
}

cin.get();

删除Global_Plugin;
删除Global_Plugin_A;
删除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天全站免登陆