2
\$\begingroup\$

I seem to be in a love-hate relationship with C++. It can make some parts of your code elegant and easy to grasp, and some others become an inconceivable mess. And it's not the language itself causing the problem, but the allure of some language constructs which can lead to many headaches in the long run.

I have a collision detection engine for different object shapes. I have a category of convex particles, which share a common base class, and (at the moment only one) non-convex shapes.

Here is an example of a few shapes, where I have replaced the true functions with some easy-to-test ones:

#include <cstdio>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/polymorphic_get.hpp>

class Convex{
public:
    virtual ~Convex(void){};
    virtual void print(void)const = 0;
};

class Sphere: public Convex{
public:
    void print(void)const{
        printf("This is a sphere!\n");
    }
};

class Cone: public Convex{
public:
    void print(void)const{
        printf("This is a cone!\n");
    }
};

Now, since I need to perform different operations based on the type, and because performance is critical, I thought boost::variant would be great for this case, so I defined:

class Complex;
typedef boost::variant<Sphere, Cone, Complex> ShapeVariant;

where class Complex is defined as:

void print_shape(const ShapeVariant& shape);
class Complex{
public:
    Complex(const std::vector<ShapeVariant>& shapes):
        shapes_(shapes)
    {}
    void print_shapes(void)const{
        printf("This is a complex!\n");
        for(const auto& shape: shapes_) print_shape(shape);
    }
    const std::vector<ShapeVariant>& get_shapes(void)const{
        return shapes_;
    }
private:
    std::vector<ShapeVariant> shapes_;
};

and print_shape's definition is:

class PrintShapeVisitor: public boost::static_visitor<void> {
public:
    void operator()(const Complex& comp){
        comp.print_shapes();
    }
    template<typename T>
    void operator()(const T& rest){
        rest.print();
    }
};
void print_shape(const ShapeVarian& shape){
    boost::apply_visitor(PrintShapeVisitor(), shape);
}

Now, this works well, but I get into trouble when I want to introduce a Hull class, which takes an ensemble of Convex objects, since I need to store these as ShapeVariant (In the original code, I need to be able to serialize these shapes):

class Hull;
typedef boost::variant<Sphere, Cone, Hull> ConvexShapeVariant;

class Hull: public Convex{
public:
    Hull(const std::vector<ConvexShapeVariant>& shapes):
        shapes_(shapes)
    {}
    void print(void)const{
        printf("This is a hull!\n");
        for(const auto& shape: shapes_) boost::polymorphic_get<Convex>(shape).print();
    }
    const std::vector<ConvexShapeVariant>& get_shapes(void)const{
        return shapes_;
    }
private:
    std::vector<ConvexShapeVariant> shapes_;
};

and ShapeVariant needs to be updated to:

typedef boost::variant<Sphere, Cone, Hull, Complex> ShapeVariant;

Now things start to get problematic, and sometimes I even need to reinterpret_cast a ConvexShapeVariant to a ShapeVariant.

One alternative would be to have an enum:

enum eShapetype{
    SPHERE,
    CONE,
    HULL,
    CONVEX,
    COMPLEX
};

and make all shapes derive from a common class Shape base class:

class Shape{
public:
   ~Shape(void){};
   virtual eShapeType type(void)const = 0;
};

where now Shape would replace ShapeVariant in functionality:

void print_shape(const Shape& shape){
    if(shape.type() < CONVEX) static_cast<const Convex&>(shape).print(); 
    else static_cast<const Complex&>(shape).print_shapes();
}

Here is a working example of this case.

In essence, what I present here are different methods to perform 'pattern matching' on the data type, a concept that is a feature of other programming languages.

So, what do you think are the pros and cons of each of these methods. Do you have any alternative recommendations? Which one would you choose if you wanted high performance (the Convex methods are going to be called very often), and preferably clean code.

\$\endgroup\$
  • \$\begingroup\$ There is never a best way. Is there anything specific you have in mind? \$\endgroup\$ – Mast Jun 1 '16 at 10:30
  • \$\begingroup\$ Or, rather, there is never a best general way. There is a best way given certain constraints. \$\endgroup\$ – Dan Pantry Jun 1 '16 at 10:31
  • \$\begingroup\$ You both are right! I edited my question. \$\endgroup\$ – Grieverheart Jun 1 '16 at 12:06
  • 1
    \$\begingroup\$ You have a little typo - void print_shape(const ShapeVarian& shape); should probably be void print_shape(const ShapeVariant& shape); (missing the t). \$\endgroup\$ – Dannnno Jun 1 '16 at 12:08
  • 1
    \$\begingroup\$ Have you looked at this paper? It seems like what you need. May be you could implement things in the paper. Here is the link for the library itself. EDIT: right link \$\endgroup\$ – Incomputable Jun 1 '16 at 17:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.