3

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?)

User
  • 62,498
  • 72
  • 186
  • 247
  • Nitpick: for someone that is finally *grasping* the difference between objects with auto and dynamic storage duration (stack allocation) you seem to forget to use pointers when you should... In the code examples, `Animal` should be `Animal*` – David Rodríguez - dribeas Nov 09 '11 at 00:00
  • I did not specify a pointer because my whole question is about value vs pointer semantic differences and I didn't feel like typing out the different options.. I understand the difference between them. – User Nov 09 '11 at 00:23

2 Answers2

7

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • If you do use a `unique_ptr`, inside the `addAnimal` function would you have to use `std::move` again, as in `zoo.push_back(std::move(a));`? Also this seems like the better option are there any downsides? – User Nov 09 '11 at 00:27
  • 1
    @User: Perfectly correct observation. The downside is that it's harder to pass the pointer around in a nice way while still retaining it in the list. You could use `get()` in some situations, but somehow it feels icky to mix raw and managed pointers. You simply have to decide whether you need unique or shared ownership. Unique if possible, shared if necessary. – Kerrek SB Nov 09 '11 at 00:31
  • When you call `new Penguin` I notice you don't use parentheses as in `new Penguin()`. Is that a typo or recommended C++ style? – User Nov 09 '11 at 00:41
  • Not a typo. `new Penguin` is default-initialization, and `new Penguin()` is value-initialization. You should use whichever applies, though for class types, both invoke the default constructor. Quite possibly you'll want a different constructor there anyway, though (`new Penguin(Blue, "Jim", 8.7)`). – Kerrek SB Nov 09 '11 at 00:49
  • 1
    Relevant: http://stackoverflow.com/questions/620137/do-the-parentheses-after-the-type-name-make-a-difference-with-new – R. Martinho Fernandes Nov 09 '11 at 00:56
1

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.

Todd Gardner
  • 13,313
  • 39
  • 51
  • I'd seriously counsel *against* the first approach. Ownership acquisition should only happen at very specific, auditable and self-explanatory code lines (namely in `shared_ptr` constructors/declarators/assignments), but never in some obscure function call. If you want shared pointers, pass a shared pointer as the argument; if you want a non-owning pointer, pass a naked pointer (or better, a `weak_ptr`). – Kerrek SB Nov 09 '11 at 00:29
  • I like the first method because you don't have to construct a smart pointer just to call the method, but then I guess you lose exception safety. Also, based on Kerrek SB's answer it seems it'd make more sense to have the animal list a unique_ptr, no? – User Nov 09 '11 at 00:31
  • @Kerrek - I'm not a big fan everything-must-be-wrapped style; it makes the calling code unnecessarily verbose, and taking shared_ptr everywhere makes ownership much harder to follow. unique_ptr is better, but not universally accessible and still verbose. Personally, I'd just document the interface. User - You can use unique_ptr if you want; I don't use 0x in my daily coding. Any of the above is exception safe (assuming sanely implemented); it would only not be so if you were new'ing more than one object in the same argument list. – Todd Gardner Nov 09 '11 at 00:54
  • @ToddGardner: I see your point, but if you pick up any of the one million C++ SO questions that involve some standard container of `T*` and ask about "why does this crash" you might look more kindly on smart pointers. As for the function call interface, you're probably right in general, but for an `addItem` function specifically I think passing a shared/unique pointer is about the only sensible thing. – Kerrek SB Nov 09 '11 at 00:57