Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
64 views
in Technique[技术] by (71.8m points)

c++ - std::map with a custom class that has a changing encapsulated data type

I am trying to write a generic parameter handling class, where each parameter has a name, description, and value, which is encapsulated in a class that derives from a common ParameterInterface. The following shows its definition and then two concrete parameter implementations

struct ParameterInterface {
  virtual ~ParameterInterface() = default;
  virtual std::string getName() const = 0;
  virtual std::string getDescription() const = 0;
  double getValue() { return _data; }
  friend 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 final { return _name; }
  virtual std::string getDescription() const final { return _description; }
private:
  const std::string _name = "Parameter1";
  const std::string _description = "Description of Parameter1";
};

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

The goal is to store all of these parameters within a ParameterManager class where each parameter registers itself to during creation. The following implementation takes care of that:

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

    auto obj = std::make_unique<T>(std::forward<Args>(args)...);
    std::string name = obj->getName();
    auto result = _parameters.emplace(std::move(name), std::move(obj));
    assert(result.second == true);
  }

  void printAllParametersInList() {
    for (const auto &entry : _parameters) {
      std::cout << entry.first << " (" << entry.second->getDescription() << "): ";
      std::cout << entry.second->getValue() << std::endl;
    }
  }

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

(credit goes to Darhuuk, who provided a slightly modified implementation of ParameterManager here ).

This works well and I can call my main function like so:

int main() {
  ParameterManager manager;
  manager.createParameter<Parameter1>(3.14);
  manager.createParameter<Parameter2>(2.71);

  manager.printAllParametersInList();
  return 0;
}

Each parameter is moved into the std::map in ParameterManager and I am able to print all values through the printAllParametersInList() method. This works fine if I only consider double as the type to store data. How would I be able to store different elements in the same map? I.e., the following shows the desired behaviour for the main function:

int main() {
  ParameterManager manager;
  manager.createParameter<Parameter1>(3.14); // type still double
  manager.createParameter<Parameter2>(42); // now of type int
  manager.createParameter<Parameter3>("string argument"); // class Parameter3 would need to be defined here first (similarily to the Parameter1 and Parameter2 class).

  manager.printAllParametersInList();
  // should print:
  // Parameter1 (Description of Parameter1): 3.14
  // Parameter2 (Description of Parameter2): 42
  // Parameter3 (Description of Parameter3): string argument
  
  return 0;
}

Here is what I have tried / thought about:

  • Template ParameterInterface. This then requires ParameterManager to accept a template argument as it forms part of the signature of the std::map<std::string, std::unique_ptr<ParameterInterface<Type>>> _parameters definition. I can now instantiate different instances of ParameterManager, but each instance is still limited to one type. I want to have one instance of ParameterManager that can handle different types for the data.

  • Implement the strategy pattern for getValue() within ParameterInterface, i.e. delegate that responsibility to a GetValueStrategy superclass and implement concrete strategies for double, int and std::string. This does not work as the return type will change for the getValue() method signature. Equally, Implement a NodeType class, for which concrete implementation of IntNode, DoubleNode and StringNode exist. Return a NodeType for getValue(), however, how is the data stored then in NodeType without using templates? Does not seem possible.

  • Lastly, pull all of the implementation for the getValue() method into the parameter classes (Parameter1, Parameter2, ... ParameterN). This would cause code duplication (and thus not an ideal design) but would also not work out of the box, as the std::map is of type ParameterInterface which now does not know anything about getValue(). Potentially this issue could be solved through downcasting, but the code duplication would still persist.

Surely there must be a clean design solution here, any ideas how to achieve the above? A live example of the code can be found here

question from:https://stackoverflow.com/questions/66051363/stdmap-with-a-custom-class-that-has-a-changing-encapsulated-data-type

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...