views:

190

answers:

9

I have a set of classes that describe a set of logical boxes that can hold things and do things to them. I have

struct IBox // all boxes do these
{
    ....
}

struct IBoxCanDoX // the power to do X
{
    void x();
}

struct IBoxCanDoY // the power to do Y
{
    void y();
}

I wonder what is the 'best' or maybe its just 'favorite' idiom for a client of these classes to deal with these optional capabilities

a)

    if(typeid(box) == typeid(IBoxCanDoX))
    {
         IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
         ix->x();
    }

b)

   IBoxCanDoX *ix = dynamic_cast<IBoxCanDoX*>(box);
    if(ix)
    {
         ix->x();
    }

c)

if(box->canDoX())
{
    IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box);
    ix->x(); 
}

d) different class struct now

struct IBox
{
    void x();
    void y();
}
...
box->x(); /// ignored by implementations that dont do x

e) same except

box->x() // 'not implemented' exception thrown

f) explicit test function

if(box->canDoX())
{
   box->x();
} 

I am sure there are others too.

EDIT:

Just to make the use case clearer

I am exposing this stuff to end users via interactive ui. They can type 'make box do X'. I need to know if box can do x. Or I need to disable the 'make current box do X' command

EDIT2: Thx to all answerers

as Noah Roberts pointed out (a) doesnt work (explains some of my issues !). I ended up doing (b) and a slight variant

   template<class T>
    T* GetCurrentBox()
    {
       if (!current_box)
          throw "current box not set";
       T* ret = dynamic_cast<T*>(current_box);
       if(!ret)
          throw "current box doesnt support requested operation";
       return ret;
    }
    ...
    IBoxCanDoX *ix = GetCurrentBox<IBoxCanDoX>();
    ix->x();

and let the UI plumbing deal nicely with the exceptions (I am not really throwing naked strings).

I also intend to explore Visitor

+4  A: 

I suggest the Visitor pattern for double-dispatch problems like this in C++:

class IVisitor
{
public:
    virtual void Visit(IBoxCanDoX *pBox) = 0;
    virtual void Visit(IBoxCanDoY *pBox) = 0;
    virtual void Visit(IBox* pBox) = 0;
};

class IBox // all boxes do these
{
public:
    virtual void Accept(IVisitor *pVisitor)
    {
        pVisitor->Visit(this);
    }
};

class BoxCanDoY : public IBox
{
public:
    virtual void Accept(IVisitor *pVisitor)
    {
        pVisitor->Visit(this);
    }
};
class TestVisitor : public IVisitor
{
public:
    // override visit methods to do tests for each type.
};

void Main()
{
    BoxCanDoY y;
    TestVisitor v;
    y.Accept(&v);
}
David Gladfelter
Yes, but don't forget to research the many types of implementation of visitor pattern so that you use something appropriate to your needs. Besides the one in this answer (straight out of GoF) there's the acyclic, cooperative, and hierarchal...probably more. Each has its strengths and weaknesses so you're better off considering carefully before getting started with the wrong one.
Noah Roberts
@Noah, good points. It certainly can't hurt to understand the available options. However, the variations I've seen have been to better decouple compile-time dependencies between visitors and vistees. Test code is almost always tied directly to and compiled with the code under test, so the vanilla visitor pattern is probably sufficient.
David Gladfelter
I don't understand the relevance of test code. Test code doesn't generally use visitation at all since you test each implementation on its own.
Noah Roberts
Yeah, I misunderstood how @pm100 planned on using this. It's possible that a visitor variation may be more appropriate depending on his/her internal build and deployment strategies.
David Gladfelter
I wanted to select this as the answer becuase I liked the vistor pattern, first time I had really looked at it. SO thanks for showing it to me. I selected Noah answer becuase its what I ended up doing
pm100
@pm - you'll find that in cases of hierarchies like this that you'll use a combination of a variety of tools including 'a', 'b', and a visitor of some kind (or maybe several kinds).
Noah Roberts
+2  A: 

Of the options you've given, I'd say that b or d are "best". However, the need to do a lot of this sort of thing is often indicative of a poor design, or of a design that would be better implemented in a dynamically typed language rather than in C++.

Kristopher Johnson
dynamic typing doesn't solve anything really. You still have to do your checks and very often also have to do your casts. The dynamic_cast option gives all the power of dynamic typing that is needed.
Noah Roberts
A: 

IMO the dynamic_cast<> is the best option, though having the explicit functions canDoX() and then performing a static_cast<> is probably more efficient in terms of speed. But having to implement all the canDo... functions gets annoying very quickly.

Option 'd' is also viable depending on how meaningful some of those functions will be for different types of 'boxes'.

I'm not trying to be pedantic, but all the function prototypes you've shown need to be virtual.

Praetorian
The dynamic_cast option is actually cleaner and should be preferred over the other except in those cases when you really do find that the dynamic_cast is too slow. Adding "implements_X_interface()" functions to a base class just pollutes the whole thing and causes you to have to change the base class every time you add a new kind of thing under it.
Noah Roberts
A: 

If you are trying to call either of these classes actions from contingent parts of code, you I would suggest you wrap that code in a template function and name each class's methods the same way to implement duck typing, thus your client code would look like this.

template<class box>
void box_do_xory(box BOX){
    BOX.xory();
}
itsmyown
A: 

There is no general answer to your question. Everything depends. I can say only that:
- don't use a), use b) instead
- b) is nice, requires least code, no need for dummy methods, but dynamic_cast is a little slow
- c) is similar to b) but it is faster (no dynamic_cast) and requires more memory
- e) has no sense, you still need to discover if you can call the method so the exception is not thrown
- d) is better then f) (less code to write)
- d) e) and f) produce more garbage code then others, but are faster and less memory consuming

adf88
+1  A: 

A and B require run time type identification(RTTI) and might be slower if you are doing a lot checks. Personally I don't like the solutions of "canDoX" methods, if situations like this arise the design probably needs an upgrade because you are exposing information that is not relevant to the class.

If you only need to execute X or Y, depending on the class, I would go for a virtual method in IBox which get overridden in subclasses.

class IBox{
   virtual void doThing();
}
class IBoxCanDoX: public IBox{
   void doThing() {  doX(); }
   void doX();
}
class IBoxCanDoY: public IBox{
   void doThing() {  doY(); }
   void doY();
}

box->doThing();

If that solution is not applicable or you need more complex logic, then look at the Visitor design pattern. But keep in mind that the visitor pattern is not very flexible when you add new classes regularly or methods change/are added/are removed (but that also goes true for your proposed alternatives).

toefel
Correction: some *types* of visitors are rigid when you add new classes or change behavior. The acyclic visitor is actually fairly good at working around this kind of thing.
Noah Roberts
toefel, sadly a box can do both x and y
pm100
+1  A: 

If you are using the 'I' prefix to mean "interface" as it would mean in Java, which would be done with abstract bases in C++, then your first option will fail to work....so that one's out. I have used it for some things though.

Don't do 'd', it will pollute your hierarchy. Keep your interfaces clean, you'll be glad you did. Thus a Vehicle class doesn't have a pedal() function because only some vehicles can pedal. If a client needs the pedal() function then it really does need to know about those classes that can.

Stay way clear of 'e' for the same reason as 'd' PLUS that it violates the Liskov Substitution Principle. If a client needs to check that a class responds to pedal() before calling it so that it doesn't explode then the best way to do that is to attempt casting to an object that has that function. 'f' is just the same thing with the check.

'c' is superfluous. If you have your hierarchy set up the way it should be then casting to ICanDoX is sufficient to check if x can do X().

Thus 'b' becomes your answer from the options given. However, as Gladfelter demonstrates, there are options you haven't considered in your post.

Edit note: I did not notice that 'c' used a static_cast rather than dynamic. As I mention in an answer about that, the dynamic_cast version is cleaner and should be preferred unless specific situations dictate otherwise. It's similar to the following options in that it pollutes the base interface.

Edit 2: I should note that in regard to 'a', I have used it but I don't use types statically like you have in your post. Any time I've used typeid to split flow based on type it has always been based on something that is registered during runtime. For example, opening the correct dialog to edit some object of unknown type: the dialog governors are registered with a factory based on the type they edit. This keeps me from having to change any of the flow control code when I add/remove/change objects. I generally wouldn't use this option under different circumstances.

Noah Roberts
why doesnt (a) work?
pm100
@pm - because if the variable is a subclass of the interface then you won't get that typeid back.
Noah Roberts
ah - i had not realized that. I assumed that it would be true if the object could be cast to that type. Once more I am expecting c++ to be ore like c# or jave
pm100
A: 

I assume that you will not only be working with one object of one type here.

I would lay out the data that you are working with and try to see how you can lay it out in memory in order to do data-driven programming. A good layout in memory should reflect the way that you store the data in your classes and how the classes are layed out in memory. Once you have that basic design structured (shouldn't take more than a napkin), I would begin organizing the objects into lists dependent on the operations that you plan to do on the data. If you plan to do X() on a collection of objects { Y } in the subset X, I would probably make sure to have a static array of Y that I create from the beginning. If you wish to access the entire of X occasionally, that can be arranged by collecting the lists into a dynamic list of pointers (using std::vector or your favorite choice).

I hope that makes sense, but once implemented it gives simple straight solutions that are easy to understand and easy to work with.

Simon
A: 

There is a generic way to test if a class supports a certain concept and then to execute the most appropriate code. It uses SFINAE hack. This example is inspired by Abrahams and Gurtovoy's "C++ Template Metaprogramming" book. The function doIt will use x method if it is present, otherwise it will use y method. You can extend CanDo structure to test for other methods as well. You can test as many methods as you wish, as long as the overloads of doIt can be resolved uniquely.

#include <iostream>
#include <boost/config.hpp>
#include <boost/utility/enable_if.hpp>

typedef char yes;      // sizeof(yes) == 1
typedef char (&no)[2]; // sizeof(no)  == 2

template<typename T>
struct CanDo {
    template<typename U, void (U::*)()>
    struct ptr_to_mem {};

    template<typename U>
    static yes testX(ptr_to_mem<U, &U::x>*);

    template<typename U>
    static no testX(...);

    BOOST_STATIC_CONSTANT(bool, value = sizeof(testX<T>(0)) == sizeof(yes));
};

struct DoX {
    void x() { std::cout << "doing x...\n"; }
};

struct DoAnotherX {
    void x() { std::cout << "doing another x...\n"; }
};

struct DoY {
    void y() { std::cout << "doing y...\n"; }
};

struct DoAnotherY {
    void y() { std::cout << "doing another y...\n"; }
};

template <typename Action>
typename boost::enable_if<CanDo<Action> >::type
doIt(Action* a) {
    a->x();
}

template <typename Action>
typename boost::disable_if<CanDo<Action> >::type
doIt(Action* a) {
    a->y();
}

int main() {

    DoX         doX;
    DoAnotherX  doAnotherX;
    DoY         doY;
    DoAnotherY  doAnotherY;

    doIt(&doX);
    doIt(&doAnotherX);
    doIt(&doY);
    doIt(&doAnotherY);
}