Avoiding RTTI in a design
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 sto开发者_如何学JAVAred 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
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.
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.
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.
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;
}
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;
}
精彩评论