0

I want to construct a std::vector containing objects derived from the Base class. I know that I can't directly push the whole object into the vector (cause of object slicing) so I use smart pointers. However, it still doesn't work. What am I doing wrong?

My code:


struct Base{
    int num1;
    explicit Base(int num1){
        this->num1 = num1;
    }
};

struct Derived : Base{
    int num2;
    explicit Derived(int num1, int num2) : Base(num1){
        this->num2 = num2;
    }
};


int main()
{
    std::vector<std::unique_ptr<Base>> someList;
    someList.push_back(std::make_unique<Derived>(100, 1));
    someList.push_back(std::make_unique<Derived>(100, 2));
    std::cout << someList[0]->num2 << std::endl; // <---- Can't access num2 !
}

  • `std::unique_ptr::operator->()` allows you to access members of `Base` only, since the return type is `Base*`; You'll need to do a cast to actually access members of `Derived`. Since you're only accessing a member of `Derived`, the choice of vector template parameter seems questionable. Maybe you could include some detail on what you're trying to do in your program. (This may actually be an XY problem) – fabian May 01 '21 at 10:48
  • You need a virtual function defined in `Base` and overridden in `Derived`. – n. m. could be an AI May 01 '21 at 10:52
  • Thanks for the hints, I'm just trying to populate a `std::vector` with elements derived from `Base`, so e.g. let's call the base class `Dog`, then I may have a vector full of `Beagle` objects or `BorderCollie` objects, but I only know if I'm dealing with Beagles of Border Collies at runtime. – ИванКарамазов May 01 '21 at 11:35
  • What kind of virtual function? @n.'pronouns'm. I'm pretty new to c++ – ИванКарамазов May 01 '21 at 11:36
  • I would recommend reading about virtual functions and in general about object oriented programming before trying to work with inheritance. It's not a small topic and the comments section is not suitable for explaining it. – n. m. could be an AI May 01 '21 at 12:17

1 Answers1

2

The Derived objects and their num2 members are there, but the type system doesn't know that (and in similar code, it might not be certain).

The type of someList[0] is std::unique_ptr<Base>, so the -> operator allows naming members of Base. In general, a unique_ptr<Base> might not point at a Derived at all, so this is the safe way.

If the Base type were polymorphic, you could use dynamic_cast to check if the object really is a Derived. To get this working, let's add a virtual destructor to Base:

struct Base{
    int num1;
    explicit Base(int num1){
        this->num1 = num1;
    }
    virtual ~Base() = default;
};

Then we can do:

int main()
{
    std::vector<std::unique_ptr<Base>> someList;
    someList.push_back(std::make_unique<Derived>(100, 1));
    someList.push_back(std::make_unique<Derived>(100, 2));
    if (auto* dptr = dynamic_cast<Derived*>(someList[0].get()))
        std::cout << dptr->num2 << std::endl;
}

For real code, it's considered better design to make use of virtual functions in Base rather than using lots of if(dynamic_cast) checks.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Thank you! So in this example how would I "make use of virtual functions in Base" to obtain the same behaviour? – ИванКарамазов May 01 '21 at 11:30
  • @ИванКарамазов add a `virtual` method in `Base` that returns an `int`, then have `Derived` override that method to return `num2`. You can then call that method using a `Base*` pointer without needing a type cast. – Remy Lebeau May 01 '21 at 20:00