Question

Suppose there's a class Human that has a list of pets. A pet can be a Cat or a Dog. In order to avoid object slicing, I declared the objects inside the list as pointers to base class Pet.

For now, when I want to make a copy of a human by calling operator=, I need to check for each pet whether it is a dog or a cat. With this approach if, for example, I add a new type of pet, let's call it Fish, I have to remember to add (inside Human::operator=) an if statement that checks if the pet is a fish.

My question is: Is it possible to implement the operator= (or the copy constructor) for the class Human without downcasting (without checking all the types a pet can be)?

class Pet {
    public:
    virtual std::string getType()=0;
};

class Cat : public Pet {
    public:
    std::string getType() {
        return "Cat";
    }
};

class Dog : public Pet {
    public:
    std::string getType() {
        return "Dog";
    }
};

class Human {
    public:
    void addPet(Pet* pet) {
        this->pets.push_back(pet);
    }

    virtual std::list<Pet*> getPets() const {
        return this->pets;
    }

    Human& operator=(const Human& other) {
        std::list<Pet*> pets=other.getPets();
        std::list<Pet*>::iterator i=pets.begin(), end=pets.end();
        for(;i!=end;i++) {
                if(dynamic_cast<Cat*>(*i)) {
                    this->pets.push_back(new Cat);
                } else if(dynamic_cast<Dog*>(*i)) {
                    this->pets.push_back(new Dog);
                }
        }
        return *this;
    }
    private:
    std::list<Pet*> pets;
};

int main()
{
   Human* joe=new Human;
   joe->addPet(new Cat);
   joe->addPet(new Dog);
   Human bob=*joe;
   delete joe;
   std::list<Pet*> pets=bob.getPets();
   std::list<Pet*>::iterator i=pets.begin(), end=pets.end();
   for(;i!=end;i++) {
    std::cout<<(*i)->getType()<<std::endl;
   }
}
Was it helpful?

Solution

Normally what you do is to implement correctly the copy constructor of each derived class (which in many cases means just leaving just the default one).

Then, you declare in your base class something like

virtual Base* clone()=0;

And in each derived class is implemented as:

virtual Base* clone() 
{
    return new Derived(*this);
} 

Now, each time you want a copy of an object of your class hierarchy just call its clone() method. This avoids slicing since the version of clone that is called is the one of the actual type, so the correct copy constructor gets called.

OTHER TIPS

Give Pet a virtual clone() method, and then call it when needed, eg:

class Pet
{
public:
    virtual ~Pet() {}
    virtual std::string getType()=0;
    virtual Pet* clone()=0;
};

class Cat : public Pet
{
public:
    std::string getType() {
        return "Cat";
    }
    virtual Pet* clone() {
        return new Cat(*this);
    }
};

class Dog : public Pet
{
public:
    std::string getType() {
        return "Dog";
    }
    virtual Pet* clone() {
        return new Dog(*this);
    }
};

class Human
{
public:
    void addPet(Pet* pet) {
        this->pets.push_back(pet);
    }

    virtual std::list<Pet*> getPets() const {
        return this->pets;
    }

    Human& operator=(const Human& other) {
        for (std::list<Pet*>::iterator i = this->pets.begin(), end = this->pets.end(); i != end; ++i) {
            delete *i;
        }
        this->pets.clear();
        std::list<Pet*> pets = other.getPets();
        for (std::list<Pet*>::iterator i = pets.begin(), end = pets.end(); i != end; ++i) {
            std::auto_ptr<Pet> pet((*i)->clone());
            this->pets.push_back(pet.get());
            pet.release();
        }
        return *this;
    }

private:
    std::list<Pet*> pets;
};

int main()
{
    Human* joe = new Human;
    joe->addPet(new Cat);
    joe->addPet(new Dog);
    Human bob = *joe;
    delete joe;
    std::list<Pet*> pets = bob.getPets();
    for (std::list<Pet*>::iterator i = pets.begin(), end = pets.end(); i != end; ++i) {
        std::cout << (*i)->getType() << std::endl;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top