3

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!

2 Answers2

1

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks @dasblinkenlight! This looks like it is exactly what I need. I'll give it a shot tomorrow morning, when it is not 3am. :) – user3077634 Dec 07 '13 at 16:24
  • quick follow-up question actually - could the CenterFinderVisitor be made to use function pointers (or some other means) to be completely generic, sort of like a 'pass-through' functionality? e.g. so if I wanted to say `cf.anything();`, it'd try and execute `s.anything();` or `b.anything();`? That would allow me to make just one derived visitor class, rather than deriving a new visitor class every time I want to implement another function... – user3077634 Dec 07 '13 at 16:31
  • @user3077634 I don't think that you could do that - not even with templates. You could probably do it with a macro, but that would be tricky to maintain. Try this out, and let me know how it works. – Sergey Kalinichenko Dec 07 '13 at 16:45
  • thanks for your help - your solution worked. :) It's still not ideal as it's introducing a lot of really similar code, but it will definitely do until the C++ books that I've ordered arrive and I can teach myself things like template metaprogramming. :) – user3077634 Dec 07 '13 at 23:54
0

This is a classic OOP question.

Have shape base class and have all shapes inherit from it. In shape declare all functions (pure virtual or just virtual) you want a shape to have:

class shape {
public:
   shape(string _name) : name(_name) {}
   virtual ~shape(); // virtual desructor
   virtual POINT getCenter() = NULL;
   virtual getName() { return name; } // example - functionality in base class

protected:
   string name;
};

class rectangle : public shape {
{
   rectangle() : shape("rectangle") {}
   virtual POINT getCenter() { return /* do math here :) */ }
};

In your MyClass class, have a pointer/ref to a shape type.

egur
  • 7,830
  • 2
  • 27
  • 47
  • I guess that would work, and I had specific derived classes defined originally but I am trying to move away from that so I can make the code more maintainable/scalable in the long run. E.g. what if there ends up being 10 different element types? Or 50? Or some other number that is dynamically determined at runtime? – user3077634 Dec 07 '13 at 16:14
  • This is as simple as it gets with OO. It's also scalable. Any new functionality can be introduced in the base class and either overridden (base implements virtual function) or derived are forced to implement (pure virtual in base class). – egur Dec 07 '13 at 18:29