Reputation: 4275
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
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