How do I implement typically typed member objects in C ++?
I have an application that creates simple music rendering animations. These animations are driven by nodes, and each node has many parameters that can be one of several types: int, float, color, etc. Parameters can either be user-specified or can be connected to the output of another node.
I am currently using a templated type along with std::function<>
for example:
#include <functional>
template <class PT>
class Param
{
public:
Param(PT value=PT()) : _value(value), _provider(nullptr) {}
void setValue(const PT &value) {_value = value;}
void setProvider(std::function<void(PT&)> provider) {_provider = provider;}
void getCurrentValue(PT &value) {
// update current member value
if (_provider)
_provider(_value);
value = _value;
}
private:
PT _value;
std::function<void(PT &value)> _provider;
};
Then I create parameters for the animated nodes like this:
class AnimationNode
{
public:
AnimationNode(Model *model = nullptr);
void evaluate();
private:
Param<int> _xoffset;
Param<int> _yoffset;
Param<float> _scale;
Param<ColorType> _color;
};
These options can be associated with a node driver such as this one:
class SublevelMeter {
public:
SublevelMeter();
void setRange(Subrange &_range);
...
std::function<void(float&)> createProviderClosure();
private:
float _level;
...
}
std::function<void(float&)> SublevelMeter::createProviderClosure() {
return [this] (float &out) {out = _level;};
}
And connect one node to another by doing something like this:
AnimationNode::connectScaleToSublevel(SublevelMeter *slm) { _scale->setProvider(slm->createProviderClosure()); }
The problem is, I want there to be an abstract type Param
that I can pass to objects, so instead of the code above, I could pass a parameter to my SublevelMeter:
SublevelMeter::connectToParam(Param *param) { param->setProvider(slm->createProviderClosure()); }
It will also help when I write the routines that create my GUI editor widgets: the editor can determine the correct type by introspecting the Param. But I'm not sure how to do this from a templated class, nor how the best way to implement introspection in C ++. (I come to this from a python design background, which perhaps prompts me to think of it in a pythonic rather than C ++ way, and if there is a better way to approach this, I'd love to hear about it!)
I'm using Qt, so I've considered using QVariant or some other Meta-Object Qt stuff, but I'm not sure how to do this, or if it would even make sense. (I don't use Boost, and while I know it has certain detergents, I'm wary of wading into those waters ...)
I am wondering what is the cleanest / best way to do this. While efficiency is a consideration (getCurrentValue () is called many times per frame while the animation is playing), I can still possibly still afford the overhead for a dynamic type.
source to share
At least the first part of your question is solvable without the abstract Param
:
class SublevelMeter {
...
template<class PT>
void connectToParam(Param<PT> *param) {
param->setProvider(createProviderClosure<PT>());
}
// specialize this for different PTs
template<class PT>
std::function<void(PT&)> createProviderClosure();
}
If you really need to manipulate dynamic lists Param
-s and don't want to use any kind of RTTI, consider using the Visitor Pattern :
class Visitor;
class ParamBase
{
public:
virtual ~ParamBase() = default;
virtual void acceptVisitor(Visitor* v) = 0;
};
template <class PT>
class Param : public ParamBase
{
public:
...
void acceptVisitor(Visitor* v) override;
};
class Visitor {
public:
virtual ~Visitor() = default;
void visit(ParamBase* p) {
p->acceptVisitor(this);
}
virtual void visitParam(Param<float>* p) = 0;
// add more functions for other Params
};
class PrintVisitor : public Visitor {
public:
void visitParam(Param<float>* p) override {
std::cout << "visited Param<float>, value = " << p->getValue() << std::endl;
}
};
template<class PT>
void Param<PT>::acceptVisitor(Visitor* v) {
v->visitParam(this);
}
int main() {
std::unique_ptr<ParamBase> p(new Param<float>(123.4f));
std::unique_ptr<Visitor> v(new PrintVisitor());
v->visit(p.get());
return 0;
}
source to share
I have implemented a simple class for you to manage the generic type. This class is implemented without using a template, so you can declare your variables and assign a value and type directly at runtime. This implementation is very simple, you should use it as a reference to develop your own solution. In the following example, I have implemented support for only three types: int, double, and char * (string C). The main function shows you how to use a generic class type to assign LVALUE and RVALUE:
#include <stdio.h>
#include <stdlib.h>
enum Types {tInteger, tDouble, tString};
class TGenericType
{
private:
char m_Value[100];
Types m_Type;
protected:
public:
void operator=(int AValue)
{
m_Type = tInteger;
sprintf(m_Value, "%d", AValue);
}
operator int()
{
// try to convert the m_Value in integer
return atoi(m_Value); // the result depend by atoi() function
}
void operator=(double AValue)
{
m_Type = tDouble;
sprintf(m_Value, "%f", AValue);
}
operator double()
{
// try to convert the m_Value in double
return atof(m_Value); // the result depends by atof() function
}
void operator=(char* AValue)
{
m_Type = tString;
strcpy(m_Value, AValue);
}
operator char*()
{
return m_Value;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
TGenericType LVar;
// int assignment LVar used as LVALUE
LVar = 10;
// int assignment LVar used as RVALUE
int i = LVar;
// Double assignment LVar used as LValue
LVar = 10.1;
// double assignment LVar used as RVALUE
double d = LVar;
// costant string assignment LVar used as RVALUE
LVar = "Ciao Mondo";
// string copying LVar used as const string RVALUE
char Buffer[100];
strcpy(Buffer, LVar);
return 0;
}
I tested above code in C ++ builder 32bit and C ++ builder (CLang) 64bit If my solution answers your question please check it as an answer.
Qiao from Italy! Angelo
source to share