5

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
  • 289,723
  • 53
  • 439
  • 496
  • 1
    possible duplicate of [C++ How to create a heterogeneous container](http://stackoverflow.com/questions/3212058/c-how-to-create-a-heterogeneous-container) – Rob Kennedy Jun 08 '11 at 05:16
  • Possible duplicate of [How can I store objects of differing types in a C++ container?](https://stackoverflow.com/questions/4738405/how-can-i-store-objects-of-differing-types-in-a-c-container) – underscore_d Oct 27 '17 at 14:01

7 Answers7

10

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;
DuckMaestro
  • 15,232
  • 11
  • 67
  • 85
  • 1
    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++... – Blender Jun 08 '11 at 04:29
  • 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. – DuckMaestro Jun 08 '11 at 08:44
  • 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 Jun 08 '11 at 15:56
  • 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 Jun 08 '11 at 16:08
  • Actually, never mind. I replaced `Object*` with `Sphere*` and it all works. Thanks for your answer! – Blender Jun 08 '11 at 16:14
9

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.

Sven
  • 21,903
  • 4
  • 56
  • 63
  • 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: http://stackoverflow.com/questions/6274136/objects-of-different-classes-in-a-single-vector/6274152#6274152 – Blender Jun 08 '11 at 04:18
  • 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. – Sven Jun 08 '11 at 04:19
  • Here's a correct one: http://stackoverflow.com/questions/6271665/objects-structure-overriding-defined-methods – Blender Jun 08 '11 at 04:21
  • 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 Jun 08 '11 at 04:25
  • 2
    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(objects[i]);`. The `dynamic_cast` will return NULL if the object was not a Sphere. – Sven Jun 08 '11 at 04:40
  • 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 Jun 08 '11 at 04:46
  • 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 Jun 08 '11 at 04:53
  • 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. – Sven Jun 08 '11 at 07:28
  • Yes, it is `virtual` on the `Object` class. – Blender Jun 08 '11 at 16:02
8

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

sergico
  • 2,595
  • 2
  • 29
  • 40
Gene Bushuyev
  • 5,512
  • 20
  • 19
  • 1
    Tree falling in the woods moment! – Lambage Jan 11 '16 at 15:06
  • 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.](https://godbolt.org/z/Gbb8Gd7nj) What do you think? – Daniel K. Feb 10 '22 at 10:24
1
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

  • 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: http://pastebin.com/raw.php?i=1KCMpP87 – Blender Jun 08 '11 at 05:26
  • 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. – thatguyoverthere Jun 08 '11 at 05:36
  • So I'll have to define all the object-specific variables within the main `Object` class? – Blender Jun 08 '11 at 15:59
  • 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 http://www.artima.com/designtechniques/compoinhP.html – thatguyoverthere Jun 08 '11 at 19:01
0

Are the objects related in a meanginful way? If they're not, then you probably shouldn't.

If they are, you'll want to do some reading on inheritance.

Chris Walsh
  • 1,863
  • 17
  • 16
0

The other posts have told you most of what you need to know. I would like to add that boost has pointer containers that might be handy as they cleanup there contents when they are destroyed. Boost manual

Eelke
  • 20,897
  • 4
  • 50
  • 76
0

Using std::variant would be the best solution if you are using C++17. If not, let me explain:

Vector of values is in principle faster than vector of pointers because of smaller cache misses. I investigated this solution and this is the basic idea. Imagine you have three types Parent, Child1 and Child2. Size of them are for instance 32 bytes, 40 bytes and 48 bytes. If you create std::vector<char[48]>, in principle you will be able to hold any of the values in it. And since Child1 and Child2 inherit from Base, you can access them through Base* and take advantage of the vtable already present in each class to polymorphically call methods in Child1 and Child2.

I created a wrapper for std::vector to do exactly this. Here it is

template <typename Parent, typename... Children>
class polymorphic_vector {
   private:
    template <typename Base, typename... Others>
    class alignas(16) polymorphic {
       private:
        static constexpr size_t round_to_closest_16(size_t size) {
            return ((size % 16) == 0) ? size : ((size / 16) + 1) * 16;
        }
        template <typename T>
        static constexpr size_t get_max_type_size() {
            return sizeof(T);
        }

        template <typename T, typename Arg, typename... Args>
        static constexpr size_t get_max_type_size() {
            return max(sizeof(T), get_max_type_size<Arg, Args...>());
        }

        static constexpr size_t max(size_t v1, size_t v2) {
            return v1 > v2 ? v1 : v2;
        }

        class wrapper {
           public:
            static constexpr int m_size = get_max_type_size<Others...>();
            char m_data[m_size];
        };

       public:
        wrapper m_wrapper;
    };

    using pointer_diff_t = int16_t;

    std::vector<polymorphic<Parent, Children...>> m_vector;
    std::vector<pointer_diff_t> m_pointer_diff;

    template <typename BaseAddr, typename ModifiedAddr>
    pointer_diff_t get_pointer_diff(BaseAddr base, ModifiedAddr modified) {
        char* base_p = reinterpret_cast<char*>(base);
        char* modified_p = reinterpret_cast<char*>(modified);
        return base_p - modified_p;
    }

    template <typename BaseAddr, typename ModifiedAddr>
    ModifiedAddr get_modified_addr(BaseAddr base, pointer_diff_t diff) {
        char* base_p = static_cast<char*>(base);
        return reinterpret_cast<ModifiedAddr>(base_p - diff);
    }

   public:
    polymorphic_vector(int size) : m_vector(size), m_pointer_diff(size) {}
    polymorphic_vector() : m_vector(), m_pointer_diff() {}

    Parent* get(int index) {
        return get_modified_addr<char*, Parent*>(
            m_vector[index].m_wrapper.m_data, m_pointer_diff[index]);
    }

    template <typename Q>
    void push_back(const Q& q) {
        static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
        static_assert(std::is_base_of<Parent, Q>::value);

        m_vector.emplace_back();
        ::new (m_vector.back().m_wrapper.m_data) Q(q);
        m_pointer_diff.emplace_back(get_pointer_diff(
            m_vector.back().m_wrapper.m_data,
            static_cast<Parent*>(
                reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
    }

    template <typename Q, typename... Args>
    void emplace_back(const Args&... args) {
        static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
        static_assert(std::is_base_of<Parent, Q>::value);

        m_vector.emplace_back();
        ::new (m_vector.back().m_wrapper.m_data) Q(args...);
        m_pointer_diff.emplace_back(get_pointer_diff(
            m_vector.back().m_wrapper.m_data,
            static_cast<Parent*>(
                reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
    }

    void shuffle() {
        std::vector<int> indexes(m_vector.size());

        std::iota(indexes.begin(), indexes.end(), 0);

        for (int i = 0; i < m_vector.size(); i++) {
            std::swap(m_pointer_diff[i], m_pointer_diff[indexes[i]]);
            std::swap(m_vector[i], m_vector[indexes[i]]);
        }
    }

    void reserve(int size) { m_vector.reserve(size); }
};

To use it, you need to specify as a template parameter the base class and the size of the internal chunk of memory that would be enough to fit any of the classes you plan to put inside.

Here is an example for Parent, Child1 and Child2:

std::vector<Parent, 48> v;

v.emplace_back<Parent>();
v.emplace_back<Child1>(param1, param2);
v.emplace_back<Child2>(param1);

v.get(0)->method1();
v.get(1)->method1();
Bogi
  • 2,274
  • 5
  • 26
  • 34