0

I am trying to store a number of objects which are derived from a base class in a std::array(or any other container) without object slicing.

The desired output from the code snippet below is:

Hi from child1!
Hi from child2!

And the code:

#include <iostream>
#include <array>
#include <memory>

class Base {
    public:
        void hello() { 
            std::cout << "This shouldn't print\n";
        }
};

class Child1 : public Base {
    public:
        void hello() {
            std::cout << "Hi from child 1!\n";
        }
};

class Child2 : public Base {
    public:
        void hello() {
            std::cout << "Hi from child 2!\n";
        }
};

std::array<std::unique_ptr<Base>, 2> array = {std::make_unique<Child1>(Child1()), std::make_unique<Child2>(Child2()) };
 
int main() {
    for (auto &i : array) {
        i->hello();
    }
}

The output I actually get from the code is:

This shouldn't print
This shouldn't print

Obviously the derived objects have been sliced. Is there any way to avoid this?

Thanks in advance for any advice you can offer.

6
  • 3
    They are not sliced, you store them as pointers. But you forgot to make the function virtual. Commented Mar 8, 2022 at 20:04
  • @OP -- This has all the earmarks of trying to use Java or similar language as a model in writing C++ code. Please don't do this. If you continue down this path, your code will suffer from one or more of these 3 things: 1) The code is buggy. 2) The code will be inefficient in terms of the coding and the running of the program. 3) The code will look weird to a C++ programmer. Commented Mar 8, 2022 at 20:09
  • Thank you, that's solved my problem. I had made the function virtual earlier but had mistakenly done std::make_unique<Base>(Child1()) which, of course, did not compile since Base is a virtual class. Commented Mar 8, 2022 at 20:11
  • 3
    @Ryan "which, of course, did not compile since Base is a virtual class" - that is not why it didn't compile. In fact, that statement does compile fine for me, whether Base has virtual methods or not. It just doesn't do what you think it does. It does not return a unique_ptr<Base> pointing to a Child1 object. It returns a unique_ptr<Base> pointing to a Base object that was passed a temporary Child1 object to its constructor. Commented Mar 8, 2022 at 20:15
  • 2
    I think you are confusing virtual and pure virtual. Having a pure virtual function in class makes it abstract (i.e., you cannot create an isntance of it). Having any virtual function makes class polymorphic, but not necessarily abstract. Commented Mar 8, 2022 at 20:15

1 Answer 1

5

Obviously the derived objects have been sliced.

Actually, no. They are not being sliced. You are doing the correct thing in storing base-class pointers to derived-class objects in your array.

No, the code doesn't work the way you want, not because of object slicing, but because you simply did not mark hello() as virtual in Base and as override in the derived classes. So, when you call i->hello() inside your loop, there is no polymorphic dispatch being performed.

Also, you are using std::make_unique() incorrectly. The parameters of make_unique<T>() are passed as-is to the constructor of T. In this case, you are copy-constructing your Child1/Child2 classes from temporary Child1/Child2 objects, which is not necessary. You can get rid of the temporaries.

You are also lacking a virtual destructor in Base. Which will cause undefined behavior when your array goes out of scope and the unique_ptrs try to call delete on their Base* pointers. Only the Base destructor will be called, but not the Child1/Child2 destructors.

Try this instead:

#include <iostream>
#include <array>
#include <memory>

class Base {
    public:
        virtual ~Base() = default;

        virtual void hello() { 
            std::cout << "This shouldn't print\n";
        }
};

class Child1 : public Base {
    public:
        void hello() override {
            std::cout << "Hi from child 1!\n";
        }
};

class Child2 : public Base {
    public:
        void hello() override {
            std::cout << "Hi from child 2!\n";
        }
};
 
int main() {
    std::array<std::unique_ptr<Base>, 2> array = {
        std::make_unique<Child1>(),
        std::make_unique<Child2>()
    };

    for (auto &i : array) {
        i->hello();
    }
}

Online Demo

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.