Nikolaos Georgiou
Nikolaos Georgiou

Reputation: 2874

C++ initialize reference with expression at constructor

Consider the following C++ classes:

// An abstract fruit that can be sliced
class AbstractFruit
{
public:
  virtual void Slice() = 0;
};

// A lemon (concrete implementation of AbstractFruit)
class Lemon : public AbstractFruit
{
int size; // small medium large

public:
  Lemon(int size) : size(size) {}
  virtual void Slice() override { /* slice it */ }
};

// A pie uses a fruit reference
class Pie
{
  AbstractFruit& fruit;

public:
  Pie(AbstractFruit& fruit) : fruit(fruit) {}  
};

This all works fine.

For backwards compatibility reasons, the Pie API needs to have a constructor that creates a lemon pie:

Pie(int size) : fruit(Lemon(size)) {}

This doesn't work. To the best of my understanding, I'm creating a new Lemon instance in the parameter list of the constructor and initializing the fruit reference with it. But, the Lemon instance gets destructed immediately after that, leaving the fruit reference pointing to a destroyed object.

What's the best way to make this work?

I understand I could switch the Pie to use a pointer instead of a reference, but I'd like to use a reference if possible.

Upvotes: 0

Views: 68

Answers (2)

Evg
Evg

Reputation: 26362

If the size of Pie object is not critical, one possible solution is to let Pie manage optional Lemon:

class Pie
{
    std::optional<Lemon> lemon_;
    AbstractFruit& fruit_;

public:
    Pie(AbstractFruit& fruit) : fruit_(fruit) {}
    Pie(int size) : lemon_(size), fruit_(*lemon_) {}
};

std::optional is optional, std::unique_ptr can be used instead:

class Pie
{
    std::unique_ptr<Lemon> lemon_;
    AbstractFruit& fruit_;

public:
    Pie(AbstractFruit& fruit) : fruit_(fruit) {}
    Pie(int size) : lemon_(std::make_unique<Lemon>(size)), fruit_(*lemon_) {}
};

In the first case, Lemon will be stored inside Pie itself, in the second case, Lemon will be allocated on heap, and Pie will store only a pointer to it. If Lemon size is small (<= sizeof(void*)), std::optional is a better option.

Note that the members are initialized in order of declaration in the class, that's why lemon_ declaration should come before fruit_ one.

Upvotes: 3

Artfunkel
Artfunkel

Reputation: 1852

A reference is just a memory address, either 32-bit or 64-bit. Where should the actual bytes of your Lemon live after you have finished calling the constructor?

Currently they live in the stack, which is cleaned up after you finish calling the constructor. To avoid this you need to provide some other storage location for your Lemon which lives at least as long as your Pie.

The easiest way to do this is to use new to allocate Lemon on the heap, and then decide when you are going to clean it up later.

Upvotes: 1

Related Questions