Objects of different classes in a single vector?

19,482

Solution 1

Sphere and Plane would need a common base type, or your vector would need to be composed of void*'s.

Common base type (better):

class Shape { ... };
class Sphere : public Shape { ... };
class Plane : public Shape { ... };

std::vector<Shape*> shapes;

or void*'s (not great):

std::vector<void*> shapes;

Solution 2

The classes would need to have a common base class, e.g.:

class MyBase { };
class Sphere : public MyBase { };
class Plane : public MyBase { };

Then in order to store polymorphic objects in a vector, you must store a pointer to them (because they can be different sizes from the base class). I recommend using a std::shared_ptr<MyBase> or std::unique_ptr<MyBase> (or use Boost if C++0x isn't available).

std::vector<std::shared_ptr<MyBase> > v;
v.push_back<std::shared_ptr<MyBase>(new Sphere());
v.push_back<std::shared_ptr<MyBase>(new Plane());

If there is no common base, you'd have to use void*, or find a different way to do this.

Solution 3

Creating containers of polymorphic types is a classical solutions, which comes with its own problems. One of which the types have to become polymorphic just in order to add them to a container -- not a good reason. Another problem is early and tight coupling resulting in more difficult maintenance and lack of flexibility, just in order to add them to a container -- not a good reason. Fortunately, in C++ there are better alternatives.

A better solution would be storing functions and not objects themselves in containers. The common reason why you want to put different types in the same container is to perform the same actions on all of them, for example, Sphere::Draw() or Plane::Draw(). What you can do is create a container of draw functions instead and erase type. E.g.

vector<function<void()>> drawings;
Sphere s;
Plane p;
drawings.push_back(bind(s, &Sphere::Draw));
drawings.push_back(bind(p, &Plane::Draw));
for(auto I = drawings.begin(); I != drawings.end(); ++i) (*i)();

By doing that you avoided strong coupling and other problems of inheritance, and got a more flexible, more general solution.

The above solution works only with C++11 as it requires std::function()

Solution 4

Class Shape{...code...}
Class Sphere : public Shape{...code...}
Class Plane  : public Shape{...code...}

std::vector<Shape*> List;
List.push_back(new Sphere);
List.push_back(new Plane);

or

//Base class to derived class
Shape* Shape_Sphere = new Sphere();
Shape* Shape_Plane  = new Plane(); 

std::vector<Shape*> List;
List.push_back(Shape_Sphere);
List.push_back(Shape_Plane);

and if you want to delete the pointers

std::vector<Shape*>::iterator it;

for(it = List.begin(); it != List.end(); ++it)
{
  delete *it;
}

Since the vector stores instances of Shape and Sphere/Plane are derived of the base class Shape, C++ will allow this to work

Share:
19,482
Blender
Author by

Blender

Need a freelancer? Send me an email at me@{username}so.33mail.com. You're welcome to skim my SO answers to get a general feel of what I've worked with. I have significant experience with Python and most of its popular packages and workflows (e.g. Django, Flask, SQLAlchemy, Numpy, Scipy, Scrapy, and asyncio), especially in the realms of network applications, web development, and data acquisition/processing.

Updated on June 09, 2022

Comments

  • Blender
    Blender almost 2 years

    In my code, I have a set of objects:

    class Sphere { ...
    class Plane { ...
    ...
    

    And I need to use a collection of them (they will all have different types) in a vector. How would I add objects of different classes to a vector?

  • Blender
    Blender almost 13 years
    I did have a base Object class setup, but it was overriding my class-specific methods with the default ones found in the class. Here's my previous question: stackoverflow.com/questions/6274136/…
  • Sven
    Sven almost 13 years
    That's a link to this question. But on a guess, I'd say the problem is that you weren't using pointers or references. You must always use pointers or references with polymorphic types.
  • Blender
    Blender almost 13 years
  • Blender
    Blender almost 13 years
    So if I were to reference an object from the vector, how would I initialize it? If I do this Object target = *objects[i];, I can't make use of the non-base class's functions.
  • Blender
    Blender almost 13 years
    When I declare an object like Sphere from the vector, is it of the type Shape or of the type Sphere? I'm asking because the vector is a scene description object which I iterate over, and the base class declaration (Shape foo = *objects[i];) overrides the subclass's (let's say a Sphere's) internal functions with the ones I declared in the base class. It's hard for me to explain, as I don't code in C++...
  • Sven
    Sven almost 13 years
    You must always use references or pointers to refer to polymorphic types. If you do Object target = *object[i]; your object gets copied into an Object and loses its original type. You can do Object &target = *object[i];, or Object *target = object[i];, which will allow you to use virtual functions. To get a particular type, use Sphere *target = dynamic_cast<Sphere*>(objects[i]);. The dynamic_cast will return NULL if the object was not a Sphere.
  • Blender
    Blender almost 13 years
    Okay, I see exactly what you mean. I tried making that change, and I get this error: error: request for member ‘intersection’ in ‘target’, which is of non-class type ‘Object*’. I'm trying to see what's wrong there...
  • Blender
    Blender almost 13 years
    I should probably go read more about this topic, as I have no idea what I'm doing... I changed my syntax from foo.bar() to foo->bar() and the error goes away, but the original problem comes back. Gah, C++, why must you be so complicated???
  • Blender
    Blender almost 13 years
    Wow, I have code which looks eerily similar to this. Right now, I am wrestling with the creating new objects and defining properties. My current code chunk looks like this: pastebin.com/raw.php?i=1KCMpP87
  • thatguyoverthere
    thatguyoverthere almost 13 years
    Let me guess that the (I'm guessing type double) radius is declared in sphere not Object? Check to make sure, because Object light is still a class Object it just happens to point to the memory of the newly allocated Sphere and can't directly access radius, Unless you didn't put radius under the reserved word "protected:" then you shouldn't be able to gain access to it until you do so.
  • Sven
    Sven almost 13 years
    Is the bar method marked virtual on Object class? Only virtual methods will use the derived class's override when called on a variable whose type is the base class.
  • Mark Rolich
    Mark Rolich almost 13 years
    When you retrieve an element from the list, it will be typed to Shape*. If you only need to call non-virtual methods in your Shape class, or virtual methods first declared in Shape, then you're done. If you need to call non-virtual methods in your subclass, or access public fields in your subclass, you will have to cast the retrieved Shape* to Sphere* before use.
  • Blender
    Blender almost 13 years
    I'm using the arrow operator (the -> thing) to call the functions, and it seems to kinda work. I'm getting better errors now, so thanks!
  • Blender
    Blender almost 13 years
    So I'll have to define all the object-specific variables within the main Object class?
  • Blender
    Blender almost 13 years
    Yes, it is virtual on the Object class.
  • Blender
    Blender almost 13 years
    If you'd like to see the error-causing code, I've edited it into my question. I have no idea what the problem with it is...
  • Blender
    Blender almost 13 years
    Actually, never mind. I replaced Object* with Sphere* and it all works. Thanks for your answer!
  • thatguyoverthere
    thatguyoverthere almost 13 years
    Yes, but remember to put radius under the "protected:" keyword if you want the derived class to have access to it. Just remember inheritance has its pros and cons vs. composition artima.com/designtechniques/compoinhP.html
  • Lambage
    Lambage over 8 years
    Tree falling in the woods moment!
  • Daniel K.
    Daniel K. about 2 years
    Two suggestions: A) bind() takes a callable object as first parameter. So it should read drawings.push_back(bind(&Sphere::Draw, s)); B) The for-loop could be written shorter as: for(auto i: drawings) i();. Test for yourself. What do you think?