0

The problem

I'm trying to support some degree of polymorphism in my code. I basically want to override a method str in subclasses (Dog and Duck) and then use a smart pointer to the superclass Animal in another class (AnimalContainer) to call the str method. Specifically, I want to support the following API:

int main(){
    Duck duck;
    Dog dog;

    AnimalContainer duckInContainer(duck);
    std::cout << duckInContainer.str() << std::endl; // Outputs "None" but should output "Duck"


    AnimalContainer dogInContainer(dog);
    std::cout << dogInContainer.str() << std::endl; // Outputs "None" but should output "Dog"
}

The lack of pointers here is deliberate. I'm intending this to be a front end of an API and want to make it as simple as possible. Therefore, if possible, I want to avoid making users explicitely create (say) a Dog pointer themselves. In other words, I'd like to avoid making users do something like this:

Dog dog;
std::unique_ptr<Dog> dog_ptr = std::make_unique<Dog>(dog);
AnimalContainer animalContainer(dog_ptr)
std::cout << animalContainer.str() << std::endl;

Is the above possible and if so, what changes do I need to make to the following code:

The code

#include <iostream>
#include <vector>
#include <memory>

using namespace std;


class Animal {

public:
    Animal() = default;

    virtual std::string str() {
        return "None";
    }

};

class Duck : public Animal {
public:
    using Animal::Animal;

    std::string str() override {
        return "Duck";
    }
};

class Dog : public Animal {
public:
    using Animal::Animal;

    std::string str() override {
        return "Dog";
    }
};

typedef std::unique_ptr<Animal> AnimalPtr;


class AnimalContainer {
private:
    AnimalPtr animal_ptr;
public:
    explicit AnimalContainer(const Animal& animal){
        this->animal_ptr = std::make_unique<Animal>(animal);
    }

    explicit AnimalContainer(AnimalPtr animal_ptr){
        this->animal_ptr = std::make_unique<Animal>(*animal_ptr);
    }

    std::string str(){
        return this->animal_ptr->str();
    }

};

So far I think that this line

this->animal_ptr = std::make_unique<Animal>(animal);

is the issue since the Dog or Duck is being sliced to an Animal. However, the alternative which is being explicit about which type of Animal we want in the pointer aka

std::unique_ptr<Dog> dog_ptr = std::make_unique<Dog>(Dog())
CiaranWelsh
  • 7,014
  • 10
  • 53
  • 106
  • What abaout ````AnimalPtr aptr = std::make_unique(Dog())````? – A M Apr 17 '20 at 16:48
  • You're `AnimalContainer` constructor is taking an object and making a copy of it, but only using the base class which results in the slice. You'll want to [add clone methods](https://stackoverflow.com/questions/5148706/copying-a-polymorphic-object-in-c) to your hierarchy. – 1201ProgramAlarm Apr 17 '20 at 17:17
  • And then `clone` in the constructor? – CiaranWelsh Apr 17 '20 at 17:30

1 Answers1

1

Without clone function, you might do:

class AnimalContainer {
private:
    AnimalPtr animal_ptr;
public:
    explicit AnimalContainer(const Dog& dog){
        this->animal_ptr = std::make_unique<Dog>(dog);
    }
    // Same for Duck

    explicit AnimalContainer(AnimalPtr animal_ptr){
        this->animal_ptr = std::move(animal_ptr));
    }

    std::string str(){ return this->animal_ptr->str(); }
};

Or more generically:

class AnimalContainer {
private:
    AnimalPtr animal_ptr;
public:
    template <typename T>
    explicit AnimalContainer(const T& animal){
        this->animal_ptr = std::make_unique<T>(animal);
    }

    explicit AnimalContainer(AnimalPtr animal_ptr){
        this->animal_ptr = std::move(animal_ptr));
    }

    std::string str(){ return this->animal_ptr->str(); }
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302