1

I have a Visual Studio 2008 C++ application with several types of objects that derive from a common base. For example:

class Base
{
public:
    std::string Time() const { /*return formatted time*/; };
private:
    SYSTEMTIME time_;
};

class Foo : public Base
{
public:
    const char* FooFunc() const { return "Hello from foo!"; };
};

typedef std::vector< Base > BaseList;

class Bar : public Base
{
public:
    const char* BarFunc() const { return "Hello from bar!"; };

    void push_back( const Base& obj ) { list_.push_back( obj ); };
    BaseList::const_iterator begin() const { return list_.begin(); };
    BaseList::const_iterator end() const { return list_.end(); };

private:
    BaseList list_;
};

These objects are stored in a std::vector< Base >. I need to output the information in each of the Foo and Bar classes as well as information stored in the base. But, I would like to avoid RTTI.

int main( int, char** )
{
    BaseList list;

    Foo foo;
    Bar bar;
    Foo foo2;

    list.push_back( foo );
    list.push_back( bar );
    bar.push_back( foo2 );

    for( BaseList::const_iterator it = list.begin();
         it != list.end();
         ++it )
    {
        printf( "%s ", it->Time() );

        // print Foo information for objects of type Foo
        // OR print Bar information for objects of type Bar.
        // Descend in to objects of type Bar to print its children.
    }
    return 0;
}

In this case, the desired output would be:

11:13:05 Hello from foo!
11:22:14 Hello from bar!
    11:26:04 Hello from foo!

What changes can I make to this design that would avoid using RTTI as a solution but still allow me to store objects like Foo and Bar with different functionality in a nested tree-like structure?

Thanks, PaulH

PaulH
  • 7,759
  • 8
  • 66
  • 143

5 Answers5

14

This is vanilla runtime polymorphism via inheritance.

Replace FooFunc and BarFunc with implementations of a pure virtual method DoTheOutput inherited from Base, and then call that method through Base* in the loop. If you need Base output too then the Base::DoTheOutput would not be pure and would get called by the subclass implementations, after they have completed derived class-specific output.

If you need to keep BarFunc and FooFunc intact in the interface, you could have them delegate to the new DoTheOutput function.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • 1
    +1. Using a pure virtual method would make `Base` abstract, making the slicing problem really obvious by causing a compile error. – Fred Larson Dec 13 '10 at 16:23
10

First, there is a problem with the Bar class. Since you're using a std::vector< Base >, you're object are sliced (that is a copy of the Base part of the object is inserted in the vector, not you're object). You'll want to use a std::vector< Base* >, that is a vector of pointers.

Secondly, to do what you want, you can use a virtual method in Base class overriden by both the Foo and the Bar class. If you think your application would need more than one of those traversal operation you can take a look to the Visitor pattern.

Sylvain Defresne
  • 42,429
  • 12
  • 75
  • 85
  • The visitor pattern is exactly what I was looking for. Thank you. – PaulH Dec 13 '10 at 16:40
  • @PaulH, @Sylvain - this approach does allow you to preserve the derived classes intact (so +1), but since they already inherit from `Base` I am curious why Visitor is preferable. It's more cumbersome, I think. – Steve Townsend Dec 13 '10 at 16:45
  • Yes, in this simple case, I also think that it is more cumbersome. But we are in the presence of composite objects (Bar instance), and if there is more than a few instance of the recursive descent, the visitor pattern may be easier to use. – Sylvain Defresne Dec 13 '10 at 16:49
  • @Steve Townsend - Visitor is for when you need to add behavior to a class that does not logically belong to the class. So if the member function really doesn't make sense as something a `Base` does (for example, if the `Base` represents an expression, applying various optimization transforms to the subtree) it should be left out and made a part of a Visitor instead. – Omnifarious Dec 13 '10 at 16:51
  • @Omnifarious - yes, I guess my point is that this seems to be a shared characteristic of every `Base` in the container. Anyway, I do like the suggestion as a way to preserve target classes. – Steve Townsend Dec 13 '10 at 16:57
  • @Steve Townsend - Personally, while I think patterns are very useful, I think they should be applied judiciously and thoughtfully. IMHO, if the target classes need a new piece of behavior to be logically consistent, then a new behavior it is. – Omnifarious Dec 14 '10 at 01:32
  • 1
    @Steve, @Omnifarious: Visitor lets you "trade" the ability to easily add classes to a (large) hierarchy for the ability to easily add virtual methods to a base in that hierarchy. Using it should be decided by thinking if you are more likely to introduce additional virtual methods (in which case use Visitor) or to introduce additional classes (in which case, don't). Adding classes when using Visitor (or virtual methods when not) is certainly possible, but it will be tedious and error-prone. – Jon Jan 07 '11 at 13:17
6

Ok, at Alf's suggestion, I'm making my comment into an answer.

Even RTTI won't work here, since you're storing Base objects in the vector. The objects will be sliced, losing any information from the derived class. You need to store Base pointers, preferably smart pointers. In addition, you have no virtual functions. RTTI requires at least one virtual function to work.

Make your function a pure virtual method in Base, then override it in each derived class with the appropriate behavior. This will make Base an abstract class, making the slicing problem impossible.

Community
  • 1
  • 1
Fred Larson
  • 60,987
  • 18
  • 112
  • 174
2

It sounds like you want to use polymorphism. Make a virtual function and have the subclasses implement it. Then make your list a list of pointers to the base:

class Base
{
public:
    virtual const char *Func() = 0; // pure virtual, no implementation
};

class Foo: public Base
{
public:
    virtual const char *Func() { return "Hello from foo!"; }
};

class Bar: public Base
{
public:
    virtual const char *Func() { return "Hello from bar!"; }
};

int main()
{
    std::list<Base*> myList;
    myList.push_back(new Foo());
    myList.push_back(new Bar());

    for( std::list<Base*>::const_iterator it = myList.begin();
         it != myList.end();
         ++it )
    {
        printf( "%s ", (*it)->Func() );
    }

// don't forget to delete your pointers.  Or better yet, use smart pointers

    return 0;
}
Niki Yoshiuchi
  • 16,883
  • 1
  • 35
  • 44
0

I have a Visual Studio 2008 C++ application with several types of objects that derive from a common base. ... These objects are stored in a std::vector< Base >. I need to output the information in each of the Foo and Bar classes as well as information stored in the base. But, I would like to avoid RTTI. ... What changes can I make to this design that would avoid using RTTI as a solution but still allow me to store objects like Foo and Bar with different functionality in a nested tree-like structure?

Important note: you say that you're using a std::vector<Base>, if that's true you'll need to change to a std::vector<Base*> (or a Boost pointer container) to avoid object slicing.

Summary: you have a base class and classes derived from it. You want the classes derived from the base class to do things based on what they actually are, but also want the base class to provide methods that make sense for all classes derived from it.

Answer: rename FooFunc and BarFunc appropriately. Have them override a virtual finction in your base class:

class Base {
public:
    std::string Time() const { /*return formatted time*/; };
    // this is a pure virtual function (the "= 0" part)
    // there are other kinds of virtual functions that would also work
    virtual const char* Func() const = 0;
private:
    SYSTEMTIME time_;
};

class Foo : public Base {
public:
    const char* Func() const { return "Hello from foo!"; };
};

typedef std::vector<Base*> BaseList;

class Bar : public Base
{
public:
    const char* Func() const { return "Hello from bar!"; };

    void push_back( const Base& obj ) { list_.push_back( obj ); };
    BaseList::const_iterator begin() const { return list_.begin(); };
    BaseList::const_iterator end() const { return list_.end(); };

private:
    BaseList list_;
};

int main( int, char** )
{
    BaseList list;

    Foo foo;
    Bar bar;
    Foo foo2;

    // you normally don't want to do it this way, but since the
    // container won't outlive the stack objects you'll be safe.
    list.push_back(&foo);
    list.push_back(&bar);
    bar.push_back(&foo2);

    for(BaseList::const_iterator it = list.begin();
        it != list.end();
        ++it )
    {
        // the "extra" * was added because the list is a vector<Base*>
        // instead of a vector<Base>
        printf("%s ", *it->Time());

        // print Foo information for objects of type Foo
        // OR print Bar information for objects of type Bar.
        printf("%s\n", *it->Func());
        // Descend in to objects of type Bar to print its children.

        // This you'll need to use RTTI to know when you're looking at a
        // Bar object and need to descend into it
        bar* = dynamic_cast<Bar*>(*it);
        if (bar == NULL)
            continue;
        for (BaseList::const_iterator bit = bar->begin(); bit != bar->end(); ++bit)
            printf("\t%s\n", bit->Func());
    }
    return 0;
}
Max Lybbert
  • 19,717
  • 4
  • 46
  • 69