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 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…