views:

382

answers:

6

I'm trying to teach myself C++, and one of the traditional "new language" exercises I've always used is to implement some data structure, like a binary tree or a linked list. In Java, this was relatively simple: I could define some class Node that maintained an instance variable Object data, so that someone could store any kind of object in every node of the list or tree. (Later I worked on modifying this using generics; that's not what this question is about.)

I can't find a similar, idiomatic C++ way of storing "any type of object." In C I'd use a void pointer; the same thing works for C++, obviously, but then I run into problems when I construct an instance of std::string and try to store it into the list/tree (something about an invalid cast from std::string& to void*). Is there such a way? Does C++ have an equivalent to Java's Object (or Objective-C's NSObject)?

Bonus question: If it doesn't, and I need to keep using void pointers, what's the "right" way to store a std::string into a void*? I stumbled upon static_cast<char*>(str.c_str()), but that seems kind of verbose for what I'm trying to do. Is there a better way?

+11  A: 

C++ does not have a base object that all objects inherit from, unlike Java. The usual approach for what you want to do would be to use templates. All the containers in the standard C++ library use this approach.

Unlike Java, C++ does not rely on polymorphism/inheritance to implement generic containers. In Java, all objects inherit from Object, and so any class can be inserted into a container that takes an Object. C++ templates, however, are compile time constructs that instruct the compiler to actually generate a different class for each type you use. So, for example, if you have:

template <typename T>
class MyContainer { ... };

You can then create a MyContainer that takes std::string objects, and another MyContainer that takes ints.

MyContainer<std::string> stringContainer;
stringContainer.insert("Blah");

MyContainer<int> intContainer;
intContainer.insert(3342);
Charles Salvia
Is it safe for me to mentally analogize templates to Java generics?
Tim
Not really. They are quite different, even if the syntax is superficially the same.
Charles Salvia
To elaborate, Java generics are a runtime mechanism that relies on inheritance and polymorphism. C++ templates are compile-time constructs which instruct the compiler to generate code for each parameterized type. See my edited answer for more information.
Charles Salvia
Awesome. Thanks!
Tim
In fact, believe it or not, C++ templates actually have more in common with C macros than Java generics. Of course, they are much safer and more powerful than macros.
Charles Salvia
@Charles: That's like saying Dr X has more in common with Dr Mangler than Dr Y. The comparison is not helpful and misleading at the same time.
Martin York
@Martin, I can see your point, but templates are so often compared to Java generics, (due to a similarity in syntax, and superficially, a similarity in function), when in reality they are drastically different. (Compile-time code generation versus run-time polymorphism.) I thought the comparison with C macros (which are also a compile-time code generation mechanism) helped highlight that. But you're right, templates are very different from macros as well, which are nothing more than a find/replace preprocessing mechanism.
Charles Salvia
@Charles: They're actually conceptually similar to Common Lisp macros, but that's not going to help many people grasp the concept.
David Thornley
When _coming_ from Java generics, I'd say that it's actually fairly safe to think about C++ templates as generics initially, since most things that you'd expect work roughtly the same way (with the exception of raw types). Of course templates are much more than that, but as a first iteration, it should do. A bigger problem is approaching Java generics as if they're C++ templates (i.e. the other direction to the author of the question).
Pavel Minaev
+3  A: 

What you are looking for are templates. They allow you to make classes and function which allow you to take any datatype whatsoever.

lhahne
A: 

You should be able to cast a void* into a string* using standard C-style casts. Remember that a reference is not treated like a pointer when used, it's treated like a normal object. So if you're passing a value by reference to a function, you still have to de-refrence it to get its address.

However, as others have said, a better way to do this is with templates

Nali4Freedom
C-style casts should be discouraged in C++.
David Thornley
+2  A: 

Templates are the static way to do this. They behave like Java and C# generics but are 100% static (compile time). If you d'ont need to store different types of objetcs in the same container, use this (other answers describe this very well).

However, if you need to store different types of objects in the same container, you can do it the dynamic way, by storing pointers on a base class. Of course, you have to define your own objects hierarchy, since there is no such "Object" class in C++ :

class Animal
{
   ~virtual Animal() {}
}    

class Dog : public Animal
{
}

class Cat : public Animal
{
}

// ...

// populate a list:
std::list< Animal*> List;
List.push_back(new Dog);
List.push_back(new Cat);

// ...

// clear the list:
for (std::list< Animal*>::iterator it = List.begin(); it!= List.end(); ++it)
{
    delete *it;
}
List.clear();

A smart pointer is easier to use. Example with boost::smart_ptr:

std::list< boost::smart_ptr<Animal> > List;
List.push_back(boost::smart_ptr<Animal>(new Dog));
List.push_back(boost::smart_ptr<Animal>(new Cat));
List.clear(); // automatically call delete on each stored pointer
Samuel_xL
+8  A: 

You can take a look at boost::any class. It is type safe, you can put it into standard collections and you don't need to link with any library, the class is implemented in header file.

It allows you to write code like this:

#include <list>
#include <boost/any.hpp>

typedef std::list<boost::any> collection_type;

void foo()
{
    collection_type coll;
    coll.push_back(boost::any(10));
    coll.push_back(boost::any("test"));
    coll.push_back(boost::any(1.1));
}

Full documentation is here: http://www.boost.org/doc/libs/1_40_0/doc/html/any.html

parti3an
+1  A: 
static_cast<char*>(str.c_str())

looks odd to me. str.c_str() retrieves the C-like string, but with type const char *, and to convert to char * you'd normally use const_cast<char *>(str.c_str()). Except that that's not good to do, since you'd be meddling with the internals of a string. Are you sure you didn't get a warning on that?

You should be able to use static_cast<void *>(&str). The error message you got suggests to me that you got something else wrong, so if you could post the code we could look at it. (The data type std::string& is a reference to a string, not a pointer to one, so the error message is correct. What I don't know is how you got a reference instead of a pointer.)

And, yes, this is verbose. It's intended to be. Casting is usually considered a bad smell in a C++ program, and Stroustrup wanted casts to be easy to find. As has been discussed in other answers, the right way to build a data structure of arbitrary base type is by using templates, not casts and pointers.

David Thornley