Upcasting an std::vector

Bossa Nova
Unregistered
 
Post: #1
Hello.

I need to find some way to upcast an entire std::vector all at once. To be more specific, I need a vector of a derrived class but the exact class will not be known at compile time -instead its read from a file.

Does anyone know the best way to solve this problem?
Quote this message in a reply
M.J.
Unregistered
 
Post: #2
I don't know if it's the best way to solve your problem, but you could have a vector of pointers:
Code:
std::vector<BaseClass*> vect;
This will allow you to have any class derived from BaseClass stored in the vector.

You have to be a bit careful storing pointers in STL containers. You'll have to allocate/free the objects yourself and you'll have to take extra steps if you plan on sorting, searching, or anything else that relies on comparisons.

M.J.
Quote this message in a reply
Moderator
Posts: 365
Joined: 2002.04
Post: #3
Well, for starters, make sure the classes that will be contained within the vector have a common base class that is well designed and allows you to avoid upcasting altogether if possible. On the other hand, if your derived classes are likely to be so different that they hardly have any common functionality, maybe they shouldn't all be held in the same vector in the first place.

What are you trying to do with the objects in the vector?

Neil Carter
Nether - Mac games and comic art
Quote this message in a reply
Bossa Nova
Unregistered
 
Post: #4
Its pretty confusing, Carter.

I have a CCharacter class. All the member variables of CCharacter are initalized by reading a file from disk for each character.

Each character can have moves which will also be read from that file. To do this I need to update the vector<CBasicMove> to be vector<CCharactersMove>. CCharacters move is ofcourse derrived from CBasicMove.

CCharactersMove doesn't add any new functions or variables but overrides a render(), processMovement(), and testCollision() function from the base. With these three methods, any move can be described.

To try and put all the moves for every character in the base class is impossible --or at least really, really, ugly.

I think I might just go M.J.'s route. I haven't actually started coding anything though (this is all still design philosophy) so I'm open to suggestions on better ways of doing this.
Quote this message in a reply
Moderator
Posts: 365
Joined: 2002.04
Post: #5
If I understand your description correctly, it sounds like you don't need to upcast at all because your base class already contains the (virtual) methods that you need to call, so you can always access the functionality though a base class pointer. That, therefore, is not your problem.

I think you're actually saying that you need to initialise each element of a vector with an object of unknown type which is derived from this common base class, and you need to know how to get this data out of a file and into an object of the right type. Just to be clear, that's not what I'd call upcasting.

Try something like this:

Give each of your derived classes a name (could be a string, could be an integer constant, your choice) that is used to identify the class in the file.

There are then a couple of different options I can think of. Firstly, when you read the class name from the file, you could just have a massive switch statement or if/elseif chain which determines the type from the name and creates the right kind of object. This is easy but crude!

Alternatively, you could make a registry class which keeps a list of names of classes and function pointers it can call to create objects for each class. Before reading any files you add each class to the registry. Then, while reading the file, every time you come across a class name you use the registry to automatically look it up and create an object of the right type. The creation function for a class (which can be a static method) is pretty simple and could look something like this:

Code:
    BaseClass *YourClass::Create()    // Must be static!
    {
        return new YourClass;
    }

If you need to load data for the objects after you create them, I suggest you call a method on the object to configure it after you've created it rather than trying to pass the data in through the constructor.

Note that I'm returning a pointer here. As M.J. said, you'll probably need to have a vector of pointers, especially if your derived classes each have different member variables and so on. That said, I think that if your classes don't contain any member variables beyond what the base class offers you can just have a vector of objects without using pointers (can anyone else confirm this?).

If you do have a vector of pointers, consider using a reference counting pointer class instead, so your declaration becomes:

Code:
    vector<some_kind_of_reference_counting_pointer<BaseClass> > classes;

That gives you the benefit of using pointers without requiring you to manually delete the contents of the vector when you want to get rid of it. Don't use auto_ptr for this purpose - it'll have entirely the wrong effect!

The second scheme I'm suggesting is similar to how CodeWarrior's PowerPlant framework loads its user interface definitions from resources. The advantage over the first scheme is that it's potentially easier to maintain (once you've got it working) because you can add new classes with one line of code instead of having to add extra code to a switch statement.

Did that make any sense? Huh If you need a better explanation, feel free to ask!

Neil Carter
Nether - Mac games and comic art
Quote this message in a reply
Bossa Nova
Unregistered
 
Post: #6
Neil,

>> "Just to be clear, that's not what I'd call upcasting."
I probably have the wrong definition, but upcasting it me is anytime you try to go from some base class to some derrived class. I'm not sure if this is correct, though.

The pdl (pre-definition language --fancy lingo for pseudo code :-) ) I wrote is basically your first solution (if/then logic with a switch).

I thought about making a registry out of it but went with the simpler route. Although this doesn't scale well, its easy.


The only thing I didn't catch was the "reference counting pointer."
vector<some_kind_of_reference_counting_pointer<BaseClass> >

This notation is foreign to me. Are you making a vector of a vector here? I don't quite follow.

Thanks, btw, for your suggestions so far.
Quote this message in a reply
Moderator
Posts: 365
Joined: 2002.04
Post: #7
Quote:Originally posted by Bossa Nova
>> "Just to be clear, that's not what I'd call upcasting."
I probably have the wrong definition, but upcasting it me is anytime you try to go from some base class to some derrived class. I'm not sure if this is correct, though.

Upcasting is when you cast a pointer or reference from a base class to a derived class. You're trying to create objects of the derived class directly.

Quote:I thought about making a registry out of it but went with the simpler route. Although this doesn't scale well, its easy.

Sounds sensible. If your system is comparatively simple, I'd go for the easy route.

Quote:The only thing I didn't catch was the "reference counting pointer."
vector<some_kind_of_reference_counting_pointer<BaseClass> >

This notation is foreign to me. Are you making a vector of a vector here? I don't quite follow.

No, it's a declaration of a template (vector) which has another template (some_kind_of_reference_counting_pointer) as its type parameter. If it were a vector of vectors, it would be declared as:

Code:
    vector<vector<Class> > theVectorOfVectors;

The thing that makes it look weird is the disembodied '>' at the end of the type. You can't write '>>' as you would with brackets because the compiler reads it as the right shift operator, so you have to put in a space to separate them.

If you want to take a look at a reference counting pointer class, you could look at the one in the Boost library. I think the one you want is called shared_ptr from their Smart Pointers Library.

Neil Carter
Nether - Mac games and comic art
Quote this message in a reply
M.J.
Unregistered
 
Post: #8
Quote:Originally posted by NCarter
...As M.J. said, you'll probably need to have a vector of pointers, especially if your derived classes each have different member variables and so on. That said, I think that if your classes don't contain any member variables beyond what the base class offers you can just have a vector of objects without using pointers (can anyone else confirm this?)...

This would not work. For example, take the following code:
Code:
#include <iostream>
#include <vector>

class BaseClass
{
public:
    virtual void DoStuff();
};

void BaseClass::DoStuff()
{
    std::cout << "BaseClass::DoStuff() was called" << std::endl;
}


class DerivedClass : public BaseClass
{
public:
    virtual void DoStuff();
};

void DerivedClass::DoStuff()
{
    std::cout << "DerivedClass::DoStuff() was called" << std::endl;
}


int main()
{
    std::vector<BaseClass> v;
    DerivedClass d;

    v.push_back(d);

    v[0].DoStuff();

    return 0;
}

This prints out "BaseClass::DoStuff() was called" not "DerivedClass::DoStuff() was called". This is because the vector stores a copy of d. It gets this copy by calling the BaseClass copy constructor (since v is of type "std::vector<BaseClass>"). This will copy only the BaseClass "part" of d and the object stored in the vector will be a BaseClass instance. It never knows that it came from an instance of DerivedClass.

Although I think everbody probably gets it, we could change the main() function to the following:
Code:
int main()
{
    DerivedClass* d = new DerivedClass;

    {
        std::vector<BaseClass*> v;
        v.push_back(d);


        v[0]->DoStuff();
    }

    // must make sure d lives longer than the vector that references it
    delete d;


    return 0;
}
And we get "DerivedClass::DoStuff() was called" as output.

M.J.
Quote this message in a reply
Moderator
Posts: 365
Joined: 2002.04
Post: #9
Quote:Originally posted by M.J.
It gets this copy by calling the BaseClass copy constructor (since v is of type "std::vector<BaseClass>"). This will copy only the BaseClass "part" of d and the object stored in the vector will be a BaseClass instance. It never knows that it came from an instance of DerivedClass.

Good point. In my defense, I wrote that post immediately after waking up (which was probably a bad idea)! ZZZ

Neil Carter
Nether - Mac games and comic art
Quote this message in a reply
Post Reply