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.

+3


source to share


2 answers


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;
}

      

0


source


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

0


source







All Articles