Search code examples
c++openscenegraph

How to reference a variable without knowing the specific type?


I'm fairly new to C++, and still trying to get my head around some of the finer points of intermediate-level concepts such as templates/writing generic code. I'm writing an application using OpenSceneGraph (OSG), and basically this is what I'm trying to do:

  • I want to have a general element class that can handle any number of different element 'types'
  • Each instance of the general element class should contain a different shape (depending on the type)
  • The different element types (and the shapes they're mapped to) will only be discovered at run-time, because they're going to depend on source data - e.g. there could be 6 different element types that are all represented by boxes of different sizes. Or there could be 3 different element types - one Box, one Cylinder, one Cone.

Some background info about OSG to explain the source of my issue:

  • osg::Box and osg::Cylinder are both kinds of osg::Shape
  • both derived types have identical methods, getCenter
  • even though you can do osg::Shape myShape = osg::Box(); you can't then say myShape.getCenter(); - doesn't work on osg::Shape objects.

Here's an example of what I'm trying to do:

class MyClass {
private:
    // ???? How to reference 'shape' ??
public:
    MyClass(string _type) {
        // This is for example purposes. Eventually types & mappings will be discovered at run-time.

        if (_type == "FOO") { 
            shape = new osg::Box();
        } else if (_type == "BAR") {
            shape = new osg::Sphere();
        }
    }
    /* 
       ???? How to handle getShape()??
    */
}

int main() {
    string readFromData = "FOO";
    MyClass* myFoo (readFromData);

    string alsoFromData = "BAR";
    MyClass* myBar (alsoFromData);

    osg::Vec3f fooCenter = myFoo->getShape()->getCenter();
    osg::Vec3f barCenter = myBar->getShape()->getCenter();
}

I've tried a few different approaches but haven't quite been able to work it out:

  • creating a MyShape class that extends osg::Shape, and has a virtual function header for getCenter - but this makes MyShape an abstract class that cannot be instantiated.
  • template<typedef T> class MyClass... - but if we only discover the type & shape mappings at runtime, then what goes in the angle brackets throughout the rest of my code? e.g.: MyClass<?????????>* myFoo;
  • using boost::any to store the shape internally - but same issue basically. How do you define a getShape function that could return a pointer to one of several different types?

I can't find any previous questions that deal with this type of scenario specifically (sorry if I missed one!). If anyone can help me it'd be super awesome!


Solution

  • OSG supplies a osg::ShapeVisitor class for situations such as this one. Create a CenterFinderVisitor class that extends osg::ShapeVisitor, overriding each of its virtual member functions to retrieve the center of the corresponding shape. Pass an instance of the CenterFinderVisitor to the osg::ShapeVisitor's accept() member function on the shape instance that you store by pointer inside your class to retrieve the center, like this:

    struct CenterFinderVisitor : public osg::ShapeVisitor {
        Vec3 center;
        virtual void    apply (Sphere &s) { center = s.getCenter(); } 
        virtual void    apply (Box &b){ center = b.getCenter(); } 
        // ...and so on for other shapes
    };
    

    Now you can implement your getCenter() method as follows:

    class MyClass {
    private:
        osg::Shape *shape;
    public:
        MyClass(string _type) {
            // This is for example purposes. Eventually types & mappings will be discovered at run-time.
    
            if (_type == "FOO") { 
                shape = new osg::Box();
            } else if (_type == "BAR") {
                shape = new osg::Sphere();
            }
        }
        Vec3 getShapeCenter() {
            CenterFinderVisitor cf;
            shape->accept(cf);
            return cf.center;
        }
    };
    

    If you are not familiar with the visitor pattern, read this article on wikipedia.