首页 > 解决方案 > 可编程访问正确的虚拟班级孩子?

问题描述

我有一个虚拟/父类和这个类的许多孩子。我假设这些孩子是用一种 API 生成问题答案的不同方式——例如各种通信协议。他们可以有不同的版本。

class A {
 public: virtual const char * [] GetProtocolName () {return "A"; }
 };

class B: public A {
public: virtual const char * [] GetProtocolName () {return "B"; }
};

class C: public B {
public: virtual const char * [] GetProtocolName () {return "C"; }
};
....

假设在程序中我要列出我的子类/子列表——每个类都有一个函数:* char [] GetProtocolName()

 并基于协议:协议 1

协议 2

协议 x...

用户可以选择应由哪个类处理通信

我的问题如下:

如何在程序中 - 即编译后,以及如何将其保存在代码中,在编译之前 - 我可以确定所选类将是我的虚拟类/父级的子 X - 基于文本设置(SELECT USER in this列出类)。

问题是两件事:如何将每个可用类列为 A 类的子类,这些类在程序中可用

如何分配一个孩子 - 根据您从列表中选择的内容(即基于 * char [])从众多协议中选择一个协议?

class * communicationProtocol = ?????

我是这个主题的新手。谢谢你的任何提示。我不知道该使用什么短语,而我想要的短语给了我我已经拥有的知识。

标签: c++qtvirtualchildren

解决方案


一种常见的方法是使用“注册表”。您根据名称定义一个映射(您可以向用户询问或从文件中读取)和一个创建适当子项的函数。

以下是一个玩具但完整的示例;请注意如何main不需要知道所有可能的派生类的列表,并且仍然可以从其名称创建给定派生类类型的对象:

#include <iostream>
#include <map>
#include <string>

struct Shape {
    virtual void draw() = 0;
    virtual ~Shape() {};
};

struct Triangle : Shape {
    virtual void draw() override { std::cout << "I'm a triangle!\n"; }
};

struct Circle : Shape {
    virtual void draw() override { std::cout << "I'm a circle!\n"; }
};

std::map<std::string, Shape *(*)()> registry{
    {"triangle", []()->Shape* { return new Triangle; }},
    {"circle", []()->Shape* { return new Circle; }}
};

int main(int argc, const char *argv[]) {
    if (argc != 2) {
        std::cout << "You need to choose the shape type:\n";
        for (auto& i : registry) {
            std::cout << "  " << i.first << "\n";
        }
    } else {
        auto i = registry.find(argv[1]);
        if (i == registry.end()) {
            std::cout << "Unknown shape type\n";
        } else {
            Shape *s = i->second();
            s->draw();
            delete s;
        }
    }
    return 0;
}

您应该注意的一个微妙细节是,如果派生类是在其他编译单元中定义的,并且它们的注册也在这些单元的静态初始化期间完成,则形式上存在链接器根本不会考虑这些类的风险,除非有对单位的一些明确引用。换句话说,假设你有:

 /// File a.cpp
 #include "base.h"
 #include "registry.h"

 struct DerivedA : Base {
     ...
 };

 static int init_a = [](){
     registry["a"] = []()->Base* { return new DerivedA; };
     return 1;
 }();

以及所有其他派生类的类似文件。

然后 C++ 标准说,即使您在程序中编译并链接它们,这些类也可能不会被注册。

发生这种情况是因为init_a初始化可能会延迟到第一次访问编译单元中的任何对象完成。但是如果init_a没有初始化,那么就没有人可以访问这个类,因为这个名字不会在注册表中。不能保证静态持续时间对象的动态初始化在 开始之前执行main,您只能保证它在该编译单元中使用任何符号之前执行。

不幸的是,C++ 中没有可移植性atstart()

换句话说,使用自注册模块(即在程序中的其他任何地方都没有引用这些编译单元中的任何符号)不是可移植的 C++。它可能有效,但没有这样的保证。

为了安全起见,您应该只显式注册所有类。


推荐阅读