【问题标题】:Programmable access to the right virtual class child?可编程访问正确的虚拟班级孩子?
【发布时间】:2019-08-01 13:14:07
【问题描述】:

我有一个虚拟/父类 和这个班的许多孩子。 我假设这些孩子是用一种 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)。

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

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

class * communicationProtocol = ?????

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

【问题讨论】:

  • 要列出每个可用的类,它是 A 类的子类,您需要自己创建此列表。要获得类型(类)列表,请查看std::tuple。但我所有的想法都以一个大的 switch 语句结束,将预编译与后编译联系起来。
  • 是的,但是拥有这么大的 switch 语句(在阅读问题时我首先想到的也是)似乎不是一个伟大的架构..
  • 让你的所有代码都充满了大的 switch 语句并不是很好,尽管在某种程度上你需要一个 switch,这不是问题。也许看看工厂模式
  • 这并没有解决问题,但是返回类型为 const char*[] 的函数返回字符串文字似乎很奇怪。这样做可能有充分的理由,但该函数似乎更有可能应该是virtual const char* GetProtocolName() { return "A"; }。并且该函数应标记为const: virtual const char *GetProtocolName() const { return "A"; }
  • c++ 不支持在运行时确定可用的类名,但是您可以通过在启动时通过静态调用让每个作为子候选的类注册自己来帮助编译器应用程序,除了@generic_opto_guy 提出的手动列表

标签: c++ qt virtual children


【解决方案1】:

哦!我在这里找到了类似的东西:

namespace Ez {

std::shared_ptr<EzGraver> create(QString const& portName, int protocol) {
    qDebug() << "instantiating EzGraver on port" << portName << "with protocol version" << protocol;

    std::shared_ptr<QSerialPort> serial{new QSerialPort(portName)};
    serial->setBaudRate(QSerialPort::Baud57600, QSerialPort::AllDirections);
    serial->setParity(QSerialPort::Parity::NoParity);
    serial->setDataBits(QSerialPort::DataBits::Data8);
    serial->setStopBits(QSerialPort::StopBits::OneStop);

    if(!serial->open(QIODevice::ReadWrite)) {
        qDebug() << "failed to establish a connection on port" << portName;
        qDebug() << serial->errorString();
        throw std::runtime_error{QString{"failed to connect to port %1 (%2)"}.arg(portName, serial->errorString()).toStdString()};
    }

    switch(protocol) {
    case 1:
        return std::make_shared<EzGraverV1>(serial);
    case 2:
        return std::make_shared<EzGraverV2>(serial);
    case 3:
        return std::make_shared<EzGraverV3>(serial);
    default:
        throw std::invalid_argument{QString{"unsupported protocol '%1' selected"}.arg(protocol).toStdString()};
    }
}











#ifndef EZGRAVERV1_H
#define EZGRAVERV1_H

#include <QSerialPort>

#include <memory>

#include "ezgraver.h"

namespace Ez {

struct EzGraverV1 : EzGraver {
    using EzGraver::EzGraver;

    /*! Moves the engraver up. */
    void up() override;

...

开启 https://github.com/camrein/EzGraver/blob/master/EzGraverCore/factory.cpp

这几乎就是我要找的东西......即它没有显示索引,我知道我需要指定这个索引,但我可以看到一个关于如何创建多个协议的结构 :) 谢谢大家的回答!

【讨论】:

    【解决方案2】:

    您可以混合使用constexpr 函数和std::conditional

    为您的类添加一个 getter 函数,该函数将返回类类型(或名称,如果您愿意):

    constexpr static const char *getType()
    {
        return 0; // You should use enum here
    }
    

    然后添加一个 constexpr 函数来检查这个值(例如使用 switch case):

    constexpr bool isType() 
    {
        return Test::getType() == 0; // Do a real comparison with a templated function for example
    }
    

    然后使用std::conditional 获取您的类型:

    typedef std::conditional<isType(), int, double>::type Type1;
    

    这段代码不是这样用的,只是作为一个例子来帮助你理解我的意思

    【讨论】:

      【解决方案3】:

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

      以下是一个玩具但完整的示例;注意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++。它可能有效,但没有这样的保证。

      为了安全起见,您应该明确注册所有类。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-10
        • 1970-01-01
        • 2013-12-24
        • 1970-01-01
        • 2014-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多