0

I have one class called Animal

class Animal
{
    std::string name;
 public:

    Animal(string n)
    {
        name = n;
    }

    string getname()
    {
        return name;
    }
};

and two inherited classes Cat and Dog

class Cat : public Animal
{
public:
    Cat(string name):Animal(name)
    {}
};

class Dog : public Animal
{
public:
    Dog(string name):Animal(name){}
};

and i have a class called AnimalQueue which contains 2 lists, cat list and dog list

class AnimalQueue
{
    std::list<Cat*> cats;
    std::list<Dog*> dogs;

public:

    void Enqueue(Animal a)
    {
        if(a.getname().compare("Cat") == 0)
        {
            // what should i do here
        }
        else
        {
            // what should i do here
        }
    }

};

what i want it, when i enter cat then it should go to the cat list and same with dog also in Enqueue function. I am not sure how it would work, how can i type cast from Animal to Cat or to Dog. I tried

Cat *c = (Cat*) &a; 

but it dows not work.

int main()
{
    string name = "Cat";
    Cat c(name);

    name = "Dog";
    Dog d(name);

    AnimalQueue aq;
    aq.Enqueue(c);
    aq.Enqueue(d);
}

This is working code, you can copy paste in your editor. you can change the signature of Animal or whatever you want so it can help to make my concept clear about type casting in inheritance.

abidkhan303
  • 1,761
  • 3
  • 18
  • 31
  • 2
    a) Identifying the object type by a free-value string isn´t exactly good. b) Why don´t you make one Enqueue for Cat and one for Dog? c) The problem with your cast: You´re passing a copy of the object. A copy in terms of the copy constructor of Animal. – deviantfan Oct 12 '14 at 19:40
  • `virtual string getname();`?!? – πάντα ῥεῖ Oct 12 '14 at 19:42
  • 3
    possible duplicate of [What is the slicing problem in C++?](http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c) – Deduplicator Oct 12 '14 at 19:46
  • http://stackoverflow.com/questions/500493/c-equivalent-of-instanceof – vdenotaris Oct 12 '14 at 19:48
  • @deviantfan actually my main problem is about adding Aniaml object in Cat or Dog list. lets say i know the animal is Cat then how can i add in list, I tried Cat *c = (Cat*) &a; but does not work. – abidkhan303 Oct 12 '14 at 19:55
  • @user1642500: You can´t, because the parameter of Enqueue is a pure Animal. Because you´ve passed it as copy. Use a reference or pointer; or two separate Enqueue´s. – deviantfan Oct 12 '14 at 20:07
  • shouls i pass pointer? – abidkhan303 Oct 12 '14 at 20:22

2 Answers2

4

For reference, here's how this code might look in C++:

#include <string>
#include <utility>
#include <vector>

class Animal
{
    std::string name_;

public:
    explicit Animal(std::string name) : name_(std::move(name)) {}
    virtual ~Animal() {}
};

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

class AnimalQueue
{
    std::vector<Cat> cats_;
    std::vector<Dog> dogs_;

public:
    void Enqueue(Animal const & animal)
    {
        if (Cat const * p = dynamic_cast<Cat const *>(&animal))
        {
            cats_.push_back(*p);
        }
        else if (Dog const * p = dynamic_cast<Dog const *>(&animal))
        {
            dogs_.push_back(*p);
        }
        else
        {
            // discarding unrecognized animal
        }
    }
};

Usage:

AnimalQueue q;
q.Enqueue(Dog("dog"));
q.Enqueue(Cat("cat"));

Notes:

C++ basics:

  • Don't say using namespace std.
  • The container of choice is std::vector, not std::list.
  • Avoid implicit conversions. Everything is explicit unless required otherwise.
  • The string-sink constructor moves from its argument.
  • Adopt systematic naming conventions for class data members.
  • No need for loads of local variables; you can put arbitrary expressions into function calls.
  • My concrete classes inherit constructors for reasons of laziness. In a real setting, you may need to write your own constructors.

Polymorphism:

  • The polymorphic base has a virtual destructor.
  • Polymorphism can be used when arguments are passed as pointers or references to a base subobjects.

High-level design:

  • As written, there seems little reason to have a polymorphic base, since your entire code is static. Several distinct overloads (for Cat and Dog) would be more appropriate.
  • Polymorphic bases are needed when the concrete type cannot be known until runtime. In that case you won't have concrete-typed variables, and you will need to create objects dynamically. When that happens, you will want to pass around std::unique_ptr<Animal>, and the dynamic casts will have to be adjusted.
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • thanks for such a nice answer. What i can see here in this, it is solved in C++ 11 or 14. If i am correct then how can we solve in in earlier versions. when i am compiling this code i am getting this error C2683: 'dynamic_cast' : 'Animal' is not a polymorphic type in Enqueue(Animal const & animal) – abidkhan303 Oct 12 '14 at 20:11
  • @user1642500: No worries. It's not really an answer, but there were so many little things that I figured it might be worth pointing them out nonetheless. – Kerrek SB Oct 12 '14 at 20:13
1

There are two problems that are fairly evident in your code:

  • You are not allowing dynamic binding
  • You have a slicing problem.

Dynamic binding

Dynamic binding is when a name is bound to a function at runtime rather than compile time. If you are dealing with runtime polymorphism, using dynamic binding allows you call functions using the dynamic type of the object (Dog) rather than its static type (Animal). This is useful because when derived classes override inherited base class functions, it is preferable to call the derived class function rather than the base class one.

To achieve dynamic binding in C++, you need to incorporate virtual functions. If you use virtual functions, the compiler will delay evaluating the name of the function until runtime where it can use the object's virtual table1 to bind the name to the address of the correct function.

To give getname() dynamic binding, declare it with the virtual specifier:

virtual string getname();

If a derived class overrides this function, that will be the one that is invoked, otherwise the base class's version will be called. This virtual function can be thought of as a default implementation.

The Slicing Problem

Slicing occurs when you copy the base part of a derived class instance. This can happen for instance when you take a base class argument by value and pass in a derived class - the derived part will be "sliced" off. When that happens you only have the static part of the object off of which to call functions and access data - hence why calling getname() in Enqueue() always calls Animal::getname().

To prevent this, you must use either a reference or pointer to a base class. This prevents slicing because no copying occurs when passing around references or pointers.

Make Enqueue take an lvalue-reference to an Animal instance:

void Enqueue(Animal const& a);

Important: If you are using base class pointer to a derived instance, in order to prevent Undefined Behavior, you must give the base class a virtual destructor. Doing so allows the derived class' destructor to be called along with the base class' destructor.


This is simply an explanation of your issues, you can refer to @Kerrek SB's answer for a good solution.


1 Virtual tables are a non-standard implementation, it really depends on your system how virtual function names are resolved.

Community
  • 1
  • 1
David G
  • 94,763
  • 41
  • 167
  • 253