2

I have a question that remains after studying this example: C++ ecosystem simulator (inheritance).

Imagine that I have a similar example, where I want to work with the std::unique_ptr. Is there a way to do the dynamic casting in the example below, without having to resort to getting the raw pointer using the .get() on the std::unique_ptr? I have put two variants in the code: one (can_eat_1) that does it the old-fashioned way and one where I have a .get() in the dynamic_cast for which I wonder if it can be removed and replaced by a more elegant method (can_eat_2):

#include <iostream>
#include <memory>

struct Animal
{
    virtual ~Animal() {};
};
struct Carnivore : public Animal {};
struct Herbivore : public Animal {};

struct Wolf   : public Carnivore {};
struct Rabbit : public Herbivore {};

bool can_eat_1(Animal* predator, Animal* prey)
{
    return ( dynamic_cast<Carnivore*>(predator) && dynamic_cast<Herbivore*>(prey) );
}

bool can_eat_2(std::unique_ptr<Animal>& predator, std::unique_ptr<Animal>& prey)
{
    return ( dynamic_cast<Carnivore*>(predator.get()) && dynamic_cast<Herbivore*>(prey.get()) );
}

int main()
{
    std::unique_ptr<Animal> wolf  (new Wolf  );
    std::unique_ptr<Animal> rabbit(new Rabbit);

    std::cout << "Option 1: pass raw pointers:" << std::endl;
    std::cout << "Wolf eats rabbit = " << can_eat_1(wolf.get(), rabbit.get()) << std::endl;
    std::cout << "Rabbit eats wolf = " << can_eat_1(rabbit.get(), wolf.get()) << std::endl;

    std::cout << "Option 2: pass unique_ptr:" << std::endl;
    std::cout << "Wolf eats rabbit = " << can_eat_2(wolf, rabbit) << std::endl;
    std::cout << "Rabbit eats wolf = " << can_eat_2(rabbit, wolf) << std::endl;

    return 0;
}
Community
  • 1
  • 1
Chiel
  • 6,006
  • 2
  • 32
  • 57

2 Answers2

2

The guideline for smart pointers in function signatures is that they should appear there if and only if the function cares about the smart pointer itself, that is, the function is involved in object lifetime management.

std::unique_ptr<Foo> f();        // Factory (gives an object to the caller)
void f(std::unique_ptr<Foo> &p); // Factory via output parameter
void f(std::unique_ptr<Foo> p);  // Sink (takes an object from the caller)

In your case, the function checks a property of your animals. It doesn't care at all about their lifetime. Thus, the smart pointer should not appear in its signature.

void f(Foo const &p); // Observe a Foo
void f(Foo const *p); // Observe a Foo

Which one of the pointer or the reference you use is a matter of taste, but the usual choice here is a reference.

can_eat_3(*rabbit, *wolf);
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Where can I find this guideline? What you write makes sense to me. – Chiel Apr 13 '16 at 11:46
  • 1
    @Chiel You can find this advice on [Herb Sutter's website](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/). – Quentin Apr 13 '16 at 12:08
  • The reference is here a bit awkward as it would require either a `try` and `catch` block to catch an exception, which is not very elegant for flow control, or a recast back to pointer. So I guess I'll go for the pointers. – Chiel Apr 13 '16 at 12:39
  • @Chiel the idea would be to take the address of both parameters inside the function so `dynamic_cast` can report failure by `nullptr`, while keeping an idiomatic interface for the function. – Quentin Apr 13 '16 at 13:35
0

You could try this via references:

bool can_eat_3(Animal const& predator, Animal const& prey)
{
    return
        dynamic_cast<Carnivore*>(&predator)
        &&
        dynamic_cast<Herbivore*>(&prey);
}

and call it via:

can_eat_3(*wolf, *rabbit);

If you dereference and get the address, you can work with can_eat_1, too:

can_eat_1(&*wolf, &*rabbit);

However, I do not really consider this 'more elegant'...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Thanks for this suggestion, I agree with you that casting pointers to references in order to take their address again is indeed not so elegant. – Chiel Apr 13 '16 at 09:27
  • Consider Quentin's advice, too. He's absolutely right about that. And about the const-ness (he did not mention it explicitely, but used it in its examples). Adjusted my answer, though. – Aconcagua Apr 13 '16 at 10:21