0

I'm currently learning Object Oriented Programming and I came with this doubt:

Let's say I have an abstract class Animal. To simplify, it will have only one virtual method, talk:

class Animal {
public:
    virtual string talk() = 0;
}

And the classes Cat and Dog, both derived from the abstract class Animal:

class Cat : public Animal {
public:
    string talk(){
        return "Meow";
    }
}

class Dog : public Animal {
public:
    string talk(){
        return "Woof";
    }
}

I want to create the class Pet, which receives a Dog or a Cat as argument:

class Pet {
public:
    Pet(Animal myPet){
    this->myPet = myPet;
    }

    Animal myPet;
}

However, I cannot do this, since "Animal" is an abstract class. How can I solve this problem?

3 Answers3

0

Use references, as follows. But note that the objects of Pet will have myPet itself not a copy from it.

class Pet {
public:
    Pet(Animal& myPet):myPet{myPet}{
    }

    Animal& myPet;
}

Or use smart pointers (like std::unique_ptr<Animal>), as follows. And now you can have your unique Animal

class Pet {
public:
    Pet(std::unique_ptr<Animal>&& myPetP):myPetP{std::move(myPetP)}{
    }

    std::unique_ptr<Animal> myPetP;
};

And then you can use them, as follows

int main(){
    //with references
    Dog d;
    Pet p {d};
    std::cout << p.myPet.talk();

    //with pointers
    Pet p {std::make_unique<Dog>()};
    std::cout << p.myPetP->talk();
}

Demo using smart pointers

Demo using smart references

asmmo
  • 6,922
  • 1
  • 11
  • 25
0

You can't create an instance of abstract class. You should use pointer, like this:

class Pet {
public:
    Pet(Animal* myPet){
      this->myPet = myPet;
    }

    Animal* myPet;
}
Evgeny
  • 1,072
  • 6
  • 6
  • Changing the constructor to `Pet(Animal& myPet)` and `this->myPet = &myPet;` allows the change to be transparent. Otherwise, users of the type `Pet` need to change how they construct them. – François Andrieux Aug 14 '20 at 15:33
  • 1
    This may change the behavior of `Pet`. The original looks like it tries to copy the `Animal`. – François Andrieux Aug 14 '20 at 15:36
0

C++ doesn't allow you to have abstract classes as a member variable. Even non-abstract but polymorphic base classes are problematic, because object slicing will lead to specialization beyond the base class being ignored.

One way to work around this limitation is to switch to using pointers instead.

class Pet{
public:
    Pet(Animal* myPet){
    this->myPet = myPet
    }

    Animal* myPet;
}

In this case you would need to pay attention to destructor behavior. When the Pet class goes out of scope, is it supposed to also destroy the underlying Animal class, or should that persist? Depending on your desired behavior, you'll have to write the rest of Pet differently.

You can also use smart pointers like std::unique_ptr, if they're appropriate for the ownership behavior you want Pet to exhibit.

Nathan Pierson
  • 5,461
  • 1
  • 12
  • 30
  • *"you would need to make sure the destructor cleans up properly"* That is not correct. Nothing indicates that `Pet` takes ownership of the pointer. The original code takes the argument by value, indicating that `Pet` isn't intended to take ownership of a pointer. – François Andrieux Aug 14 '20 at 15:33
  • 1
    *"where you can't have an abstract class as a member variable"* Even if the type isn't abstract, you shouldn't have polymorphic base types as members. Do so leads to [Object slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing). – François Andrieux Aug 14 '20 at 15:35
  • True, but if you want to match the implied behavior of the original code sample, you would need to write destructor behavior in. I suppose to be more accurate, you need to explicitly consider what needs to happen to the passed-in Animal when the Pet is destroyed. – Nathan Pierson Aug 14 '20 at 15:35
  • You would first also need to construct a new instance in the constructor and copy the argument. – François Andrieux Aug 14 '20 at 15:36