将派生类的具体实现自注册到std :: map [英] Self registering concrete implementations of a derived class to a std::map

查看:59
本文介绍了将派生类的具体实现自注册到std :: map的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想生成一个抽象类,其中包含有关参数的信息.然后,每个特定参数的具体实现都应覆盖 getName() getDescription()方法.

I would like to generate an abstract class that contains information about parameters. Each concrete implementation for a specific parameter should then override a getName() and getDescription() method.

struct ParameterInterface {
  virtual std::string getName() const = 0;
  virtual std::string getDescription() const = 0;
  double getValue() { return _data; }
  std::ostream& operator<<(std::ostream& os, const ParameterInterface& pInterface)
  {
      os << pInterface._data;
      return os;
  }
protected:
  double _data;
};

struct Parameter1 : public ParameterInterface {
  Parameter1(double data) {
    this->_data = data;
  }
  virtual std::string getName() const override final { return _name; };
  virtual std::string getDescription() const override final {
    return std::string("Description of Parameter1");
  };
private:
  const std::string _name = "Parameter1";
};

到目前为止,一切都很好.接下来,我想使用某种形式的 Parametermanager 类,该类允许 ParameterInterface 的每个具体实现将自身注册到该类.例如,这就是我大概想要的东西:

so far so good. Next I would like to have some form of Parametermanager class that allows each concrete implementation of the ParameterInterface to register itself to that class. For example, this is what I would roughly want to have:

struct ParameterManager {
  static void registerParameter(std::string name, ParameterInterface parameter) {
    _parameters.insert(std::pair<std::string, ParameterInterface>(name, parameter));
  }

  static void printAllParameters() {
    for (const auto &entry : _parameters)
      std::cout << entry.first << ", " << entry.second << std::endl;
  }
private:
  static std::map<std::string, ParameterInterface> _parameters;
};

这不能编译,因为ParameterInterface是抽象的,在这里使用唯一指针通过dynamic_cast进行向下转换会起作用吗?

This does not compile as ParameterInterface is abstract, would downcasting through dynamic_cast using a unique pointer work here?

为了实现上述目标,我的第一个想法是将 Parameter1 类的构造函数修改为类似

In order to achieve the above, my first idea was to modify the constructor of the Parameter1 class to be something like

struct Parameter1 : public ParameterInterface {
  Parameter1(double data) {
    this->_data = data;
    ParameterManager::registerParameter(_name, *this);
  }
  // ...
};

但我不确定是否可以像这样传递* this指针...这里的目标是我想生成多个参数(该参数可能随我的项目而增长,但可能超过100个),但是我不想使用类似工厂的实例类型

but I am not sure if I can pass the *this pointer around like that ... The goal here is that I would like to generate several parameters (which may grow with my project but potentially much more than 100) but I don't want to have a factory-like instantiation of the type

class parameterFactory {
// ...
  void createParameter(std::string parameter) {
    if (parameter == "Parameter1")
      // ...
    else if (parameter == "Parameter1")
      // ...
    // many more else if statements ...
  }
// ...
};

,这将导致很长的if/else语句,并且每次添加新参数时都需要修改内部结构(违反了Open/Closed原理).我想要的完全有可能通过工厂设计模式实现(我是新来的),如果可以的话,如果有人可以向我指出这一点,我将不胜感激.

which would cause a very long if/else statement and requires the internal structure to be modified each time a new parameter is added (violating the Open/Closed principle). It is entirely possible that what I want can be achieved with a factory design pattern (I am new to this), if so, I would appreciate if someone could point that out to me.

要了解我希望在功能上实现什么目标,请考虑以下 main 函数:

To get an idea of what I would like to achieve functionality wise, consider this main function:

int main() {
  Parameter1 p1(3.14);
  Parameter1 p2(2.71);
  Parameter1 p3(1.00);
  ParameterManager::printAllParameters();
  return 0;
}

当前,这仅适用于double(如果代码可以编译),理想情况下,应该将其作为模板并与任何类型一起使用.任何关于如何实现这种结构的想法都将受到欢迎!

Currently this only works with doubles (if the code would compile), ideally it should be templated and work with any type. Any ideas on how to achieve this structure would be welcome!

完整示例可在此处找到: https://godbolt.org/z/an3n9e

The full example can be found here: https://godbolt.org/z/an3n9e

推荐答案

首先,是的,如果要将不同派生类型的对象存储到映射中,则必须使用某种指针. std :: unique_ptr 在这里会很好.

First of all, yes, if you want to store objects of different derived types into a map, you'll have to use some sort of pointer. An std::unique_ptr would do fine here.

现在,我建议使用 std :: unique_ptr ,因为它们会在需要时自动处理销毁对象的任务.但是,这意味着您无法像在示例中那样进行注册.IE.您不能在其构造函数中注册对象,因为 ParameterManager 会以某种方式包含该对象的 std :: unique_ptr ,而该对象也会例如在堆栈上.这意味着您将获得双倍的免费.

Now, I would suggest an std::unique_ptr, since they automatically take care of destructing the objects when required. However, that means you can't do registration the way you do in your example. I.e. you can't have an object register itself in its constructor, because then the ParameterManager would somehow contain an std::unique_ptr to that object, while the object would also e.g. be on the stack. And that means you'll get a double free.

因此,对我来说,尚不清楚您要使用这些类的确切程度.在这种情况下,我可能会做这样的事情:

So it's not entirely clear to me how exactly you want to use these classes. In this case, I would probably do something like this:

#include <cassert>
#include <map>
#include <memory>
#include <string>
#include <utility>

class ParameterManager {
  public:
    template <class T, class... Args>
    T & createParameter (Args &&... args) {
      static_assert(std::is_base_of<ParameterInterface, T>::value,
        "T must derive from ParameterInterface");

      // Check if T already exists in map, etc.
      ...

      // Good to go, create & insert a T.
      auto obj = std::make_unique<T>(std::forward<Args>(args)...);
      std::string name = obj->getName();
      // Note that we can't do obj->getName() within the emplace call, 
      // since the std::move(obj) might be executed before the getName().
      // In which case you're calling getName() on a nullptr.
      auto result = _parameters.emplace(std::move(name), std::move(obj));

      // If there's a static method that returns the name, then the above becomes:
      auto result = _parameters.emplace(T::staticName(),
        std::make_unique<T>(std::forward<Args>(args)...));

      assert(result.second == true);
      return *(result.first->second);
    }

  private:
    std::map<std::string, std::unique_ptr<ParameterInterface>> _parameters;
};

// Example use:
ParameterManager manager;
auto & p1 = manager.createParameter<Parameter1>(3.02);

请注意,在我的示例中任何地方都没有 static .IE. ParameterManager 类不是单例.因此,您可能有多个 ParameterManager 对象,它们全部包含不同的 Parameter 对象.当然,这意味着可能必须在整个地方传递您的 ParameterManager 对象.

Note that there's no static anywhere in my example. I.e. the ParameterManager class is not a singleton. So you could have multiple ParameterManager objects all containing different Parameter objects. Of course that means potentially having to pass your ParameterManager objects all over the place.

如果您确实需要某种全局管理器,则必须修改管理器代码以仅具有静态功能&成员(这将有效地使其成为单例),或实例化全局静态 ParameterManager 对象.

If you do need some sort of global manager, then you'll either have to modify the manager code to only have static functions & members (which will effectively make it a singleton), or instantiate a global static ParameterManager object.

这篇关于将派生类的具体实现自注册到std :: map的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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