Reputation: 169
I'm in the situation where I'm not sure which type of smart pointer to use because I'm not sure of all the use cases for my class. I could just use shared pointers but I'm not fond of the idea of passing shared pointers everywhere in my code when I don't necessarily need shared ownership. This article by Herb Sutter says that when in doubt, use unique_ptr and convert to shared_ptr when you have to. This is what I'd like to do but I'm unclear as to how this is supposed to be done, consider this example:
class Example
{
public:
Example(): _ptr(std::make_unique<Node>()) {}
std::unique_ptr<Node>& getPtr()
{
return _ptr;
}
private:
// I am unsure if I will eventually need shared ownership or not
std::unique_ptr<Node> _ptr;
};
Example* example = new Example();
// Some function somewhere
void f()
{
// I've decided I need shared ownership, converting
std::shared_ptr<Node> ptr(std::move(example->getPtr()));
// Oops, example is no longer valid...
}
If someone has a better idea of how to deal with situations like this I'd be glad to hear it.
Upvotes: 3
Views: 17044
Reputation: 1563
Shared pointers are for shared resources. If you want to share resources I would declare everything shared from the beginning and not go through special conversions.
If you want to use unique pointer you are saying that there "must" only exist one pointer for the the resource you have allocated. That does not limit your way in using these objects. Why not rather use unique pointer and "move" your object through the classes instances and functions... If you have to convert to your pointer why not move it as unique...
Regarding performance: It is said, that compared to a primite pointer, the shared pointer is about 6 times slower. The unique pointer is about twice as slow. Of course I'm talking about accesses. If you really have performance critical portions of the code you can still get primitive pointers of these classes and operate. It does not even have to be a pointer, there are still the C++ References.
I think you should decide whether your object will be "shared" or "moved" and go on with the paradigm, instead of converting and changing all the time.
std::unique_ptr<Node>& getPtr()
{
return _ptr;
}
No, no. You are passing multiple references if you call that from different places. Use the "move" operator instead (With declarations like type& operator=(type&& other)
for example) and pass your unique pointer.
Upvotes: 0
Reputation: 70382
I think you are asking a kind of optimization question. You want Example
to use unique_ptr
because it has simpler and more efficient semantics (paraphrasing your referenced article). But, when the need arises, you wish to allow the pointer to be converted to shared_ptr
.
Example
should simply provide an interface for that, and itself needs to convert from unique_ptr
to shared_ptr
, when its user invokes that interface. You could use state pattern to capture whether the instance is in unique_ptr
mode or shared_ptr
mode.
class Example
{
struct StateUnique;
struct StateShared;
struct State {
State (std::unique_ptr<State> &s) : _state(s) {}
virtual ~State () = default;
virtual Node & getPtr () = 0;
virtual std::shared_ptr<Node> & getShared() = 0;
std::unique_ptr<State> &_state;
};
struct StateUnique : State {
StateUnique (std::unique_ptr<State> &s)
: State(s), _ptr(std::make_unique<Node>()) {}
Node & getPtr () { return *_ptr.get(); }
std::shared_ptr<Node> & getShared() {
_state = std::make_unique<StateShared>(*this);
return _state->getShared();
}
std::unique_ptr<Node> _ptr;
};
struct StateShared : State {
StateShared (StateUnique &u)
: State(u._state), _ptr(std::move(u._ptr)) {}
Node & getPtr () { return *_ptr.get(); }
std::shared_ptr<Node> & getShared() { return _ptr; }
std::shared_ptr<Node> _ptr;
};
public:
Example(): _state(std::make_unique<StateUnique>(_state)) {}
Node & getNode() { return _state->getPtr(); }
std::shared_ptr<Node> & getShared() { return _state->getShared(); }
private:
std::unique_ptr<State> _state;
};
If the state machine looks scary (which it should, since it is over-engineered), then you can just maintain two pointers in the Example
, and your methods which need to test which one it needs to use.
class Example
{
public:
Example(): _u_node(std::make_unique<Node>()) {}
Node & getNode() { return _u_node ? *_u_node.get() : *_s_node.get(); }
std::shared_ptr<Node> & getShared() {
if (_u_node) _s_node = std::move(_u_node);
return _s_node;
}
private:
std::unique_ptr<Node> _u_node;
std::shared_ptr<Node> _s_node;
};
Upvotes: 2
Reputation: 1597
If you have a class, say UniqueResourceHolder
, it may be implemented as follows:
class UniqueResourceHolder{
public:
std::unique_ptr<Widget> ptr;
};
Later, if you want to get said resource from UniqueResourceHolder
and put it in a SharedResourceHolder
that looks as follows:
class SharedResourceHolder{
public:
std::shared_ptr<Widget> ptr;
};
the code to do so may look like this:
{
UniqueResourceHolder urh;
SharedResourceHolder srh;
//initialization of urh.ptr
srh.ptr = std::shared_ptr<Widget>(urh.release());
}
UniqueResourceHolder
will no longer have the Widget
it once did, so any attempt to use urh
to get at the widget will be invalid (unless you repopulate it with a new one), but srh
now will have that widget and will be willing to share. Anyway, that's how I understand the answer to the question in the link you provided. My own two cents is that it is also a trivial matter to replace occurrences of std::unique_ptr
with std::shared_ptr
; any business logic your program followed to ensure the uniqueness of the resource is still valid, but to go from business logic where you took advantage of the shared-nature of std::shared_ptr
would take some time and focus to rework to a unique mindset.
Upvotes: 1