问题
I present my question in this simple form:
class animal {
public:
animal() {
_name="animal";
}
virtual void makenoise(){
cout<<_name<<endl;
}
string get_name(){
return _name;
}
protected:
string _name;
};
class cat : public animal {
public:
cat() {
this->_name="cat";
}
};
class dog : public animal {
public:
dog() {
this->_name = "dog";
}
};
I want to store all animal types together in a single container such as:
vector<animal*> container;
barnyard.push_back(new animal());
barnyard.push_back(new dog());
barnyard.push_back(new cat());
At some point in my code, I need to convert a dog object into a cat object. And all I need from this converting is to set up a fresh dog object and replace it at the same index number as a cat counterpart was located. As I understood, dynamic_cast
wouldn't work in this case and based on C++ cast to derived class, it's mentioned that such a conversion is not a good practice. Since cat and dog in my model have distinct behavioral properties, I don't want to put their definitions into the animal model. On the other hand, storing them separately in different vectors would be difficult to handle. Any suggestions?
回答1:
You say:
I need to convert a dog object into a cat object.
But then:
And all I need from this converting is to set up a fresh dog object and replace it at the same index number as a cat counterpart was located.
Do you need to convert it or replace it?? That's a completely different operation.
To convert you need to setup a function that will take a dog and return a cat:
auto convertDogToCat(Dog const& dog) -> Cat {
auto cat = Cat{};
// fill cat's member using dog's values...
return cat;
}
But to replace simply reassign with a new one:
// v--- a cat is currently there
barnyard[ii] = new Dog{};
// ^--- we replace the old pointer
// with one that points to a dog.
But that creates a memory leak, to remove the leak, simply use std::unique_ptr
:
#include <memory> // for std::unique_ptr
// The base class need a virtual destructor
class animal {
public:
virtual ~animal() = default;
// other members...
};
std::vector<std::unique_ptr<animal>> barnyard;
barnyard.emplace_back(std::make_unique<animal>());
barnyard.emplace_back(std::make_unique<dog>());
barnyard.emplace_back(std::make_unique<cat>());
barnyard[ii] = std::make_unique<Dog>();
回答2:
Here’s an alternative approach. Doesn’t use OOP or dynamic dispatch, but provides equal functionality to your sample. Also much faster, because no dynamic memory is required to allocate/free, animals are single bytes.
enum struct eAnimalKind : uint8_t
{
Generic = 0,
Cat = 1,
Dog = 2,
};
string get_name( eAnimalKind k )
{
static const std::array<string, 3> s_names =
{
"animal"s, "cat"s, "dog"s
};
return s_names[ (uint8_t)k ];
}
void makenoise( eAnimalKind k )
{
cout << get_name( k ) << endl;
}
If your classes keep more state than a type, use one class with that enum as a member.
If some animals use custom set of fields/properties it gets tricky but still possible, nested structures for specie-specific state, and std::variant of these structures inside class animal
to get track on the specie and keep the data. In this case you no longer need enum eAnimalKind
, std::variant already tracks the type it contains.
Classic C++ OOP requires dynamic memory. Derived classes generally have different sizeof, you can’t keep them in a single vector you can only keep pointers, and in runtime you’ll hit RAM latency on accessing every single element.
If your animals are large and complex i.e. megabytes of RAM and expensive methods, that’s fine. But if your animals are small, contain a couple of strings/numbers, and you have a lot of them, RAM latency will ruin the performance of OOP approach.
来源:https://stackoverflow.com/questions/57042518/a-need-for-dynamic-cast-of-a-derived-class-looking-for-an-alternative-approach