Question

There was an interesting problem in C++, but it was more about architecture.

There are many (10, 20, 40, etc) classes describing some characteristics (mix-in classes), for example:

struct Base { virtual ~Base() {} };

struct A : virtual public Base { int size; };
struct B : virtual public Base { float x, y; };
struct C : virtual public Base { bool some_bool_state; };
struct D : virtual public Base { string str; }
// ....

The primary module declares and exports a function (for simplicity just function declarations without classes):

// .h file
void operate(Base *pBase);

// .cpp file
void operate(Base *pBase)
{
    // ....
}

Any other module can have code like this:

#include "mixing.h"
#include "primary.h"

class obj1_t : public A, public C, public D {};
class obj2_t : public B, public D {};

// ...
void Pass()
{
    obj1_t obj1;
    obj2_t obj2;

    operate(&obj1);
    operate(&obj2);
}

The question is how do you know what the real type of a given object in operate() is without using dynamic_cast and any type information in classes (constants, etc)? The operate() function is used with a big array of objects in small time periods and dynamic_cast is too slow for it and I don't want to include constants (enum obj_type { ... }) because this is not the OOP-way.

// module operate.cpp

void some_operate(Base *pBase)
{
    processA(pBase);
    processB(pBase);
}

void processA(A *pA)
{
}

void processB(B *pB)
{
}

I cannot directly pass a pBase to these functions. And it's impossible to have all possible combinations of classes, because I can add new classes just by including new header files.

One solution that came to mind, in the editor I can use a composite container:

struct CompositeObject
{
    vector<Base *pBase> parts;
};

But the editor does not need time optimization and can use dynamic_cast for parts to determine the exact type. In operate() I cannot use this solution.

So, is it possible to avoid using a dynamic_cast and type information to solve this problem? Or maybe I should use another architecture?

Was it helpful?

Solution

The real problem here is about what you are trying to achieve.

Do you want something like:

void operate(A-B& ) { operateA(); operateB(); }

// OR

void operate(A-B& ) { operateAB(); }

That is, do you want to apply an operation on each subcomponent (independently), or do you wish to be able to apply operations depending on the combination of components (much harder).

I'll take the first approach here.

1. Virtual ?

class Base { public: virtual void operate() = 0; };

class A: virtual public Base { public virtual void operate() = 0; };
void A::operate() { ++size; } // yes, it's possible to define a pure virtual

class obj1_t: public A, public B
{
public:
  virtual void operate() { A::operate(); B::operate(); }
};

Some more work, for sure. Notably I don't like the repetition much. But that's one call to the _vtable, so it should be one of the fastest solution!

2. Composite Pattern

That would probably be the more natural thing here.

Note that you can perfectly use a template version of the pattern in C++!

template <class T1, class T2, class T3>
class BaseT: public Base, private T1, private T2, private T3
{
public:
  void operate() { T1::operate(); T2::operate(); T3::operate(); }
};

class obj1_t: public BaseT<A,B,C> {};

Advantages:

  • no more need to repeat yourself! write operate once and for all (baring variadic...)
  • only 1 virtual call, no more virtual inheritance, so even more efficient that before
  • A, B and C can be of arbitrary type, they should not inherit from Base at all
  • edit the operate method of A, B and C may be inlined now that it's not virtual

Disadvantage:

Some more work on the framework if you don't have access to variadic templates yet, but it's feasible within a couple dozen of lines.

OTHER TIPS

First thing that comes to mind is asking what you really want to achieve... but then again the second thought is that you can use the visitor pattern. Runtime type information will implicitly be used to determine at what point in the hierarchy is the final overrider of the accept method, but you will not explicitly use that information (your code will not show any dynamic_cast, type_info, constants...)

Then again, my first thought comes back... since you are asking about the appropriateness of the architecture, what is it that you really want to achieve? --without knowledge of the problem you will only find generic answers as this one.

The usual object oriented way would be to have (pure) virtual functions in the base class that are called in operate() and that get overridden in the derived classes to execute code specific to that derived class.

Your problem is that you want to decide what to do based on more than one object's type. Virtual functions do this for one object (the one left of the . or ->) only. Doing so for more than one object is called multiple dispatch (for two objects it's also called double dispatch), and in C++ there's no built-in feature to deal with this.

Look at double dispatch, especially as done in the visitor pattern.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top