user695652
user695652

Reputation: 4275

C++11 way of storing a polymorphic object in a class

Before C++11, I would have implemented a class Foo, containing a polymorphic object of base-class Bar as:

struct Foo {

  Bar* m_b;

  Foo(Bar* b) : m_b(b) {}

}; 

Following Scott Meyer's advice that raw pointers are bad, I would like to implement Foo in the C++11 way, but I don't know how this would look.

I started writing it like

struct Foo {

  std::unique_ptr<Bar> m_b;

  Foo(std::unique_ptr<Bar> b) : m_b(std::move(b)) {}

};

but this has the drawback that b has to be dynamically allocated.

So what is the C++11 way of implementing a class containing a polymorphic object?

Upvotes: 1

Views: 136

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275415

The argument that smart pointers are better than raw pointers is ownership based.

The ownership of a resource should be clear from the type system, as it is an easy thing to get wrong, and a hard thing to understand from just reading code. With it embedded in the type system, you can tell what determines lifetime of an object from its type.

The pre-C++11 code either did not own the Bar (it was not responsible for its lifetime), or your code leaked.

The post-C++11 code owns the Bar and is in charge of its lifetime.

If you want a state where the Foo conditionally owns the Bar based off possibly complex runtime logic, and sometimes does not, then the std smart pointers are probably not a good idea.

Imagine you had a car prior to the car safety rules. It had a button that ejected the passenger from the car. This was fun, and so long as you didn't push the button, was safe.

Now car safety rules are here, and you hear following them is "better". You come to the car safety Q&A and ask how you can set up a button that ejects your passenger from the car and follow these car safety rules. They don't seem to permit passenger ejection. Even if you put a cover on the button, there is something about "what if the car gets in an accident and the button executes incorrectly" and life and limb and other stuff.

How can I have an ejector seat in my car yet get a 5 star safety rating?

The smart pointers make ownership clear. If you have muddled ownership, then you must clarify it in order to use smart pointers.

There are situations that jusify complex ownership and lifetime semantics. These are few and far between, and by using smart pointers by default we can avoid the complexities of lifetime without having to validate it every time.

struct Foo {
  std::unique_ptr<Bar> m_b;

  Foo(std::unique_ptr<Bar> b) : m_b(std::move(b)) {}
};

This is a better interface, because the ctor of Foo makes it clear it takes ownership. Taking a raw pointer, and then wrapping it in a smart pointer, makes the interface unclear.

Upvotes: 4

Related Questions