0

My goal is to store elements of different data types in one vector. A ball as well as an cuboid is an object.

//header.h

struct Object {
};

struct Ball : Object {
  int diameter;
  int surface;
};

struct Cuboid : Object {
  int length;
  int width;
  int height;
  int surface;
};

std::vector<Object *> myObjects;
//source.cpp
myObjects.push_back(new Ball);

Now I would like to access the members of ball by:

myObjects[0]->diameter = 8; //not possible

The IDE only shows me the option accessing constructor of objects:

myObjects[0]->Object

I found some useful articles:

A true heterogenous container in c++
SO: Vector that can have 3 different data types C++

Do I have to go this way? I really just would like to store ball and cuboid in one vector and later on access members of ball and cuboid.

KeepRunning85
  • 173
  • 10
  • 1
    "I really just would like to store ball and cuboid in one vector and later on access members of ball and cuboid." How do you know which is which? – Caleth May 18 '21 at 09:47
  • when you decide for polymorphism you should not work against it but rather with it. Accessing a `Ball` member of a `Object*` is the opposite of polymorphism. – 463035818_is_not_an_ai May 18 '21 at 10:02

5 Answers5

4

You need to think about why you need to access diameter.

Once you figure that out, you should add a virtual member function for Object that would use that member. Then, you override that virtual member function in Ball, and use the diameter there.

Example:

struct Object {
    virtual float
    volume() const = 0;
    
    virtual ~Object() = default;
};

struct Ball : Object {
    virtual float
    volume() const override {
        float r = diameter / 2.f;
        return 4.f / 3.f * pi * r * r * r;
    }

private:
    int diameter;
};

struct Cuboid : Object {
    virtual float
    volume() const override {
        return 1.f * length * width * height;
    }

private:
    int length;
    int width;
    int height;
};

Use a constructor to initialise the members:

struct Ball : Object {
    Ball(int diameter) : diameter(diameter) {}
    // ...


std::vector<std::unique_ptr<Object>> myObjects;
myObjects.push_back(std::make_unique<Ball>(8));

P.S. Avoid bare owning pointers. You'll get a memory leak or worse.

P.P.S. If you tried to delete the derived object through the base pointer, the behaviour of the program would be undefined. To avoid that, define a virtual destructor for base (as shown in my example).

P.P.P.S. Avoid dynamic_cast based polymorphism. Prefer virtual functions when possible.

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

Just for completion, C++ provides dynamic casting for use cases like that. When you have a pointer to a superclass, and need to know whether it actually points to a specific class object, you can use dynamic_cast:

myObjects.push_back(new Ball);
...
Ball * b = dynamic_cast<Ball *>(myObjects[0]); // b will point to a valid Ball or be NULL
if (b != NULL) {
    b->diameter = 8;
}

Of course, polymorphism is more object centric, but dynamic casting proves to be useful in real world programs.


But this can only work if at least one method is declared to be virtual in order to enable polymorphism on pointers (thanks to @AyxanHaqverdili for the head up):

struct Object {
    virtual ~Object() {};
};
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

You could use std::variant:

#include <variant>
#include <vector>

// this helper is from https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};

template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

struct Ball {
  int diameter;
  int surface;
};

struct Cuboid {
  int length;
  int width;
  int height;
  int surface;
};

using Object = std::variant<Ball, Cuboid>;

int main() {
  std::vector<Object> myObjects;
  myObjects.emplace_back(Ball{});

  std::visit(overloaded([](Ball& ball) { ball.diameter = 0; },
                        [](Cuboid& cuboid) { /* smth else */ }),
             myObjects[0]);
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
0

For your case you can

  1. Try using std::variant https://en.cppreference.com/w/cpp/utility/variant
  2. Try casting to the objects of interest e.g. (Ball*)myObjects[0].

Second option is not what polymorphism should look like. So you might also want to reflect on why you need such a vector in the first place.

-1

You can add enum to identify type

#include <vector>

struct Object {
    enum class Type {
        Ball,
        Cuboid
    };
    Type type;
protected:
    Object(Type t) : type(t) {}
};

struct Ball : Object {
    Ball() : Object(Object::Type::Ball) {}

    int diameter;
    int surface;
};

struct Cuboid : Object {
    Cuboid() : Object(Object::Type::Cuboid) {}

    int length;
    int width;
    int heigth;
    int surface;
};

int main() {
    std::vector<Object *> myObjects;
    myObjects.push_back(new Ball);
    if (myObjects[0]->type == Object::Type::Ball)
        (static_cast<Ball *>(myObjects[0]))->diameter = 10;
    return 0;
}
Rushikesh Talokar
  • 1,515
  • 4
  • 16
  • 32