开发者

Maintain reference to any object type in C++?

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 开发者_C百科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?


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);


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


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


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++ :

#include <list>

class Animal {
public:
   virtual ~Animal() {}
};

class Dog : public Animal {
public:
   virtual ~Dog() {}
};

class Cat : public Animal {
public:
   virtual ~Cat() {}
};

int main() {
    std::list<Animal*> l;
    l.push_back(new Dog);
    l.push_back(new Cat);

    for (std::list<Animal*>::iterator i = l.begin(); i!= l.end(); ++i)
        delete *i;

    l.clear();

    return 0;
}

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


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜