Reputation:
I made a toy example of a problem I'm facing with my code:
I have an animal which I don't know what will be after a later stage, so I initialize it to a generic animal.
But later on, I want to make it a cat, so I'm assigning myAnimal
to be a Cat
#include <iostream>
class Animal {
public:
int weight;
virtual void Sound() {
// To be implemented by child class
}
};
class Cat : public Animal {
public:
void Sound() {
std::cout << "Miau" << std::endl;
}
// Only cats purr
void Purr() {
std::cout << "Purr" << std::endl;
}
};
int main() {
// At this point I don't know which animal I'll have, so I initialize it
// to a generic Animal
Animal* myAnimal;
animal->weight = 10;
// At this point of the code, I know what animal I want, so I assign animal
// to be a Cat
double selectedAnimal = 0;
if (selectedAnimal == 0) {
myAnimal = &Cat();
// myAnimal = new Cat(); // this will just create a new Cat, losing
// the already assigned weight.
// I want to "upgrade" my generic animal, keeping its properties and adding
// new ones specific to cats
}
myAnimal->Sound();
myAnimal->Purr(); // ERROR: Class Animal has no member Purr
return 0;
}
I think I'm not assigning correctly myAnimal
to be a Cat
, but it is still an Animal
. Howver the compiler doesn't complain when I do myAnimal = &Cat();
.
So I don't understand if the compiler allows me to assign Animal to the class Cat myAnimal = &Cat();
why it complains when I try to use a method specific of the class Cat.
How should I reassign my generic animal in such a way that is now a full Cat with all its methods?
EDIT:
Answering some comments:
-Animal should not have a Purr method, only cats purr.
-I don't know at compile time what Animal will I have, that's why I assign it to be generic at the beginning.
I can reassign myAnimal to be a new Cat, but then any variables already set to the generic animal will be lost (eg: Animal might have a weight variable already set before knowing it's a Cat)
I'll try the suggested down-casting by @Some programmer dude
Upvotes: 0
Views: 403
Reputation: 1091
The answer from Matthias Grün shows how to manipulate C++ to do what you want. However, my advice is to stop making C++ do what you think is right, and do it the way C++ wants to do it. C++ wants you to never throw the type of an object away. It is a strongly-typed language. It almost always an "anti-pattern" to throw away the type of an object. One common technique for avoiding this anti-pattern, is to separate "ownership" from "use". You can use a pointer to unknown-type, easily. Owning an object by a pointer to unknown type is really hard.
int main()
{
Cat my_cat;
Animal* any_animal = &my_cat; // non-owning pointer.
any_animal->Sound();
my_cat.Purr();
}
Upvotes: 1
Reputation: 1186
Firstly, when creating the Cat ensure that you allocate the memory correctly. Currently you are taking the address of a temporary object i.e. &Cat()
which is not valid C++.
You can do this in two different ways:
// On the stack
Cat cat;
myAnimal = &cat;
// OR
myAnimal = new Cat(); // On the heap (remember to free the cat)
Then, when you want to use the animal as a cat, you can use a downcast e.g.:
auto myCatPtr = dynamic_cast<Cat*>(myAnimal);
if (myCatPtr) {
// This means the pointer is valid
myCatPtr->Purr();
}
Here is a working example.
Upvotes: 1
Reputation: 1506
All that myAnimal = &Cat();
does is create a temporary of type Cat
and assign the address of it to myAnimal
, leaving you with a dangling pointer. myAnimal
will point to an invalid address afterwards.
Also, even if it had been correctly assigned, for example by writing
myAnimal = new Cat{};
it would still require a cast so the compiler knows that it's dealing with a Cat
, for example like so:
auto pCatInstance = dynamic_cast<Cat*>(myAnimal);
if (pCatInstance != nullptr)
pCatInstance->Purr();
Upvotes: 0