Say I have a base class Animal
and its derived classes Dog
and Cat
, like this
class Animal{ public:
virtual void makeSound(){
std::cout << "I dont know what sound to make." << std::endl;
}
};
class Dog: public Animal{ public:
void makeSound() override{
std::cout << "Woof!" << std::endl;
}
};
class Cat: public Animal{ public:
void makeSound() override{
std::cout << "Meow!" << std::endl;
}
};
The initial requirement is that a copy of a Dog
or Cat
object be made in a function whose input is an object of Animal
.
If the user gives a Dog
object to the function, inside it another Dog
object should be created.
Something like
void MakeSound(Animal& animal){
Animal another_animal = animal;
another_animal.makeSound();
}
int main(){
Dog dog;
MakeSound(dog);
// I want the output to be "Woof!",
// but this will only give "I dont know what sound to make."
// because of slicing.
}
Deepseek told me to use smart pointers.
class Animal {
public:
virtual void makeSound(){
std::cout << "I dont know what sound to make." << std::endl;
}
virtual ~Animal() = default; // Newly added line
virtual std::unique_ptr<Animal> clone() const = 0; // Newly added line
};
class Dog : public Animal {
public:
virtual void makeSound() override{
std::cout << "Woof!" << std::endl;
}
std::unique_ptr<Animal> clone() const override { // Newly added line
return std::make_unique<Dog>(*this); // Newly added line
} // Newly added line
};
class Cat : public Animal {
public:
virtual void makeSound() override{
std::cout << "Meow!" << std::endl;
}
std::unique_ptr<Animal> clone() const override { // Newly added line
return std::make_unique<Cat>(*this); // Newly added line
} // Newly added line
};
void MakeSound(Animal& animal){
auto another_animal = animal.clone(); // Newly added line
another_animal.makeSound();
}
This seems to make sense, but I haven't tried it, because the requirement is changed suddenly.
Now instead of making one copy, I have to make a std::vector
of copies, whose size is determined in runtime.
Something like
void AllMakeSound(Animal& animal, int num_animals){
std::vector<Animal> animals(num_animals);
for ( int i = 0; i < num_animals; i++ ){
animals[i] = animal.clone();
}
for ( int i = 0; i < num_animals; i++ ){
std::cout << "Animal " << i << " out of " << num_animals << std::endl;
animals[i].makeSound();
}
} // This will cause slicing too, so this function will not achieve the goal.
Now Deepseek cannot give much help without typeid
comparison, something like
if ( animal.getType() == typeid(Dog) )
However, I cannot allow this, because this will need to enumerate all possible derived classes and compare, while I expect users to derive the Animal
base class and create their own derived classes.
Their derived classes cannot be anticipated.
Perhaps someone would make a Godzilla
class.
Therefore, I am here to seek help. The solution should:
- Not need to enumerate all derived classes (This may excludes
template
andtypeid
comparison) - (Optionally) And produce
std::vector
that can be used as a normal one, including methods likepush_back
,empty
,[]
,reserve
.
Thanks for any help!
Say I have a base class Animal
and its derived classes Dog
and Cat
, like this
class Animal{ public:
virtual void makeSound(){
std::cout << "I dont know what sound to make." << std::endl;
}
};
class Dog: public Animal{ public:
void makeSound() override{
std::cout << "Woof!" << std::endl;
}
};
class Cat: public Animal{ public:
void makeSound() override{
std::cout << "Meow!" << std::endl;
}
};
The initial requirement is that a copy of a Dog
or Cat
object be made in a function whose input is an object of Animal
.
If the user gives a Dog
object to the function, inside it another Dog
object should be created.
Something like
void MakeSound(Animal& animal){
Animal another_animal = animal;
another_animal.makeSound();
}
int main(){
Dog dog;
MakeSound(dog);
// I want the output to be "Woof!",
// but this will only give "I dont know what sound to make."
// because of slicing.
}
Deepseek told me to use smart pointers.
class Animal {
public:
virtual void makeSound(){
std::cout << "I dont know what sound to make." << std::endl;
}
virtual ~Animal() = default; // Newly added line
virtual std::unique_ptr<Animal> clone() const = 0; // Newly added line
};
class Dog : public Animal {
public:
virtual void makeSound() override{
std::cout << "Woof!" << std::endl;
}
std::unique_ptr<Animal> clone() const override { // Newly added line
return std::make_unique<Dog>(*this); // Newly added line
} // Newly added line
};
class Cat : public Animal {
public:
virtual void makeSound() override{
std::cout << "Meow!" << std::endl;
}
std::unique_ptr<Animal> clone() const override { // Newly added line
return std::make_unique<Cat>(*this); // Newly added line
} // Newly added line
};
void MakeSound(Animal& animal){
auto another_animal = animal.clone(); // Newly added line
another_animal.makeSound();
}
This seems to make sense, but I haven't tried it, because the requirement is changed suddenly.
Now instead of making one copy, I have to make a std::vector
of copies, whose size is determined in runtime.
Something like
void AllMakeSound(Animal& animal, int num_animals){
std::vector<Animal> animals(num_animals);
for ( int i = 0; i < num_animals; i++ ){
animals[i] = animal.clone();
}
for ( int i = 0; i < num_animals; i++ ){
std::cout << "Animal " << i << " out of " << num_animals << std::endl;
animals[i].makeSound();
}
} // This will cause slicing too, so this function will not achieve the goal.
Now Deepseek cannot give much help without typeid
comparison, something like
if ( animal.getType() == typeid(Dog) )
However, I cannot allow this, because this will need to enumerate all possible derived classes and compare, while I expect users to derive the Animal
base class and create their own derived classes.
Their derived classes cannot be anticipated.
Perhaps someone would make a Godzilla
class.
Therefore, I am here to seek help. The solution should:
- Not need to enumerate all derived classes (This may excludes
template
andtypeid
comparison) - (Optionally) And produce
std::vector
that can be used as a normal one, including methods likepush_back
,empty
,[]
,reserve
.
Thanks for any help!
Share Improve this question edited Feb 17 at 18:48 张亦弛 asked Feb 17 at 18:37 张亦弛张亦弛 337 bronze badges 15 | Show 10 more comments2 Answers
Reset to default 1As an optional way, replacing the virtual part of Animal
to data.
Then dogs and cats will be instance of Animal
, making them easier to use.
In your example case, the simplest implementation would be as below. No smart_pointer, no inheritance (so no slicing).
class Animal
{
public:
Animal( std::string Sound ) : m_Sound(Sound) {}
void makeSound() const { std::cout << m_Sound << std::endl; }
private:
std::string m_Sound; //data
};
int main()
{
Animal Dog( "Woof!" );
Animal Cat( "Meow!" );
//copy into vector
std::vector<Animal> animals{ Dog, Dog, Cat };
//
for( const auto &animal : animals ){ animal.makeSound(); }
return 0;
}
Here, the "data" can be some other types(fuction pointer, functor, smart_pointer of some polymorphismic type, etc) if necessary. For example:
#include <functional>
class Animal
{
public:
Animal( std::function<std::string(void)> SoundSource ) : m_SoundSource(SoundSource) {}
void makeSound() const { std::cout << m_SoundSource() << std::endl; }
private:
std::function<std::string(void)> m_SoundSource; //data
};
//main
int main()
{
Animal Dog( []()->std::string{ return "Woof!"; } );
Animal Cat( []()->std::string{ return "Meow!"; } );
//copy into vector
std::vector<Animal> animals{ Dog, Dog, Cat };
//
for( const auto &animal : animals ){ animal.makeSound(); }
return 0;
}
The issue is in this function:
void MakeSound(Animal& animal){
Animal another_animal = animal;
another_animal.makeSound();
}
Specifically, the first line of the function where you create another_animal
. By creating a concrete Animal
object, you have sliced out the child class portion, which is why you get the base class behavior. There is only a base class object at that point.
Had you directly used animal
, as in animal.makeSound();
, the function behaves as you would expect. And while polymorphism through references is legal and has it's place and all that, it's much more common to use pointers to base objects instead.
For your request of a std::vector
, pointers are the way to go. Because we want the vector to clean up on its own, a vector of smart pointers to Animal
s will be used.
If we didn't use smart pointers, we'd be on the hook to delete
all the Animal
s manually, and that's bug-prone.
My local playground uses the fmt
library for printing, but swapping in your own printing calls is trivial.
Finally, I made a subjective change where the public member function make_sound()
is not virtual, but relies on a private virtual function to get the required information. It's not really needed for this example, but it's a habit and can provide more freedom in more complex projects. The principle is called NVI, or non-virtual interface.
#include <fmt/core.h>
#include <memory>
#include <string_view>
#include <vector>
class Animal {
public:
void make_sound() const { fmt::println("{}", get_sound()); }
private:
virtual std::string_view get_sound() const { return "..."; }
};
class Dog : public Animal {
std::string_view get_sound() const override { return "Woof!"; }
};
class Cat : public Animal {
std::string_view get_sound() const override { return "Meow."; }
};
void speak(Animal const& animal) { animal.make_sound(); }
void all_speak(std::vector<std::unique_ptr<Animal>> const& animals) {
for (auto const& a : animals) {
a->make_sound();
}
}
int main() {
Dog dog;
speak(dog);
std::vector<std::unique_ptr<Animal>> critters;
critters.push_back(std::make_unique<Dog>());
critters.push_back(std::make_unique<Cat>());
all_speak(critters);
}
Output:
Woof!
Woof!
Meow.
The information you were given about clone()
methods refers to the Protype pattern. It is a design pattern that allows you to make copies of an object without needing to know the underlying type. While I can see how a chatbot thought it was relevent, it's overkill here. Simple polymorphism is all that's needed.
While I understand that the question contains a requirement for a copy, the code presented makes me think that it was a misunderstanding or misstated requirement.
std::vector<std::unique_ptr<Animal>> animals
... – Jarod42 Commented Feb 17 at 18:42animals[i]->makeSound();
– Igor Tandetnik Commented Feb 17 at 18:43MakeSound()
you go out of your way slice your object and remove the child (Dog
in your specific example) portion when you assignanimal
toanother_animal
. It seems like you need to go back to your learning resources, which are hopefully not YouTube or a chatbot. – sweenish Commented Feb 17 at 18:44clone()
methods) has nothing to do with a simple virtual function override. – sweenish Commented Feb 17 at 18:48