问题
As I make the transition from C# to C++ I get a lot of recommendations to use value semantics where possible. It's pretty much guaranteed that if I post a question with a pointer anywhere someone will come along and suggest that it should be a value instead. I'm starting to see the light and I have found a lot of places in my code where I could replace dynamic allocation and pointers with stack allocated variables (and usually references). So I think I have a grasp on using stack allocated objects and passing them to other functions as references when the object lifetime is longer in the caller than the callee.
However I have a question about passing objects by value when the callee will take ownership. Take the following example:
class Zoo
{
void AddAnimal(Animal animal);
std::list<Animal> animals_;
}
Typically from a flexibility and unit testing perspective I'd want Animal to be an interface (abstract class in C++) so I can easily send arbitrary animals and mock it out with a mock implementation.
In a pointer implementation client code would be calling this like:
Animal animal = new Lion("Bob");
myZoo.AddAnimal(animal);
Here the client code doesn't really need the animal object. It's just constructing it temporarily to pass to the method. So in this case there aren't shared semantics. So it seems like a good case for value semantics. However, my understanding is that you can't use Animal as a parameter passed by value because it's an abstract class.
Most of my member functions that don't take primitive types take abstract class parameters. So what is the C++ method to handle this problem? (That is how do you program to interfaces in C++ with value semantics?)
回答1:
The typical solution for your scenario would involve a resource-managing handler object which you do pass by value. Popular candidates are shared_ptr
and unique_ptr
:
#include <list>
#include <memory>
#include "all_zoo_animals.h" // yours
typedef std::shared_ptr<Animal> AnimalPtr; // see note
typedef std::list<AnimalPtr> AnimalCollection;
AnimalCollection zoo;
void addAnimal(AnimalPtr a)
{
zoo.push_back(a);
}
int main()
{
AnimalPtr a = AnimalPtr(new Penguin);
a.feed(fish);
addAnimal(a); // from local variable, see note
addAnimal(AnimalPtr(new Puffin)); // from temporary
}
If it is feasible, you could also define AnimalPtr
as std::unique_ptr<Animal>
, but then you have to say addAnimal(std::move(a));
. This is more restrictive (as only one object handles the animal at any given time), but also lighter-weight.
回答2:
When you are dealing with polymorphism, you'll want to use pointers to a class instead of the class directly. This stems from the difference between static and dynamic types. If you have:
void AddAnimal(Animal animal) { /* blah */ }
Within "blah", the object animal has both a static and a dynamic type of Animal, meaning that is just an Animal, and only an Animal. If instead you take a pointer:
void AddAnimal(Animal *animal);
Then you know animal's static type, but it's dynamic type is free to vary, so the function can take /any/ animal.
Personally, I'd use one of the following three calling conventions:
class Zoo
{
// This object takes ownership of the pointer:
void AddAnimal(Animal* animal);
std::list<shared_ptr<Animal>> animals_; (or boost::ptr_list)
// This object shares ownership with other objects:
void AddAnimal(shared_ptr<Animal> animal);
std::list<shared_ptr<Animal>> animals_;
// Caller retains ownership of the pointer:
void AddAnimal(Animal* animal);
std::list<Animal*> animals_;
}
Dependent on the rest of the codebase, how the Zoo would be used, etc.
来源:https://stackoverflow.com/questions/8058625/c-object-parameters-polymorphism-value-semantics-object-lifetimes