3

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.

jxh
  • 69,070
  • 8
  • 110
  • 193
navark
  • 163
  • 1
  • 1
  • 10
  • 2
    unique_ptr and shared_ptr are for OWNERSHIP of the memory involved, not for simple use of it. You don't pass shared_ptr's around often -- as they are relatively expensive to make, compared to normal pointers/references. – xaxxon Sep 08 '16 at 17:40
  • Will `Example` *always* need to maintain ownership over its node, and sometimes other objects also require ownership of that node? – jaggedSpire Sep 08 '16 at 17:40
  • 1
    From the above example, it seems your pointer should be a `shared_ptr` as you want to (potentially) share ownership. Doubt may happen for factory. – Jarod42 Sep 08 '16 at 17:40
  • I am unsure about the intent of your question. Are you asking how to make `example` participate in the sharing of its converted `unique_ptr`? – jxh Sep 08 '16 at 17:41
  • 6
    `getPtr` is a bad interface. You should not be passing a `unique_ptr` around like that. If someone wants access to the pointer, they should get a `Node*`. – Nicol Bolas Sep 08 '16 at 17:42
  • @xaxxon Okay but what if I don't know if I'll need shared ownership or not like a library used by different programs that might or might not decide to share ownership of that object – navark Sep 08 '16 at 17:47
  • @jxh yes, or if you have another solution to this problem – navark Sep 08 '16 at 17:49
  • Can `_ptr` outlive `Example` instance ? – Jarod42 Sep 08 '16 at 17:49
  • @Jarod42 It's possible. But entire programs may use this class by reading the pointed object and never taking ownership. I'm just not sure and I would like to follow the advice in the blog article but I'm not sure how. – navark Sep 08 '16 at 17:54
  • @navark But, do you expect to be able to unshare the pointer in the future? Because, I don't think that will be possible. – jxh Sep 08 '16 at 17:54
  • @jxh no I don't necessarily expect that. Let's say a program decides it needs shared ownership it converts it to shared pointer and from there it's shared. – navark Sep 08 '16 at 18:00
  • It seems you ask something similar to "is std::vector::resize could be `const` ?", and answer that user may use same size, so don't modify the vector... but user may modify it. It seems it is the same case for your pointer. – Jarod42 Sep 08 '16 at 18:00
  • I believe Herb Sutter is talking about object *producers*. You should always *deliver* a new object in a `std::unique_ptr` so that clients can *release* that into a *shared pointer* (or other pointer of choice) if they need to. Meanwhile you maintain exception safety. – Galik Sep 08 '16 at 19:00

3 Answers3

2

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;
};
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Thank you this is essentially what I wanted to do however I wanted to use this method a lot in my code (not just this one class) and this pattern is too complicated for that. However this is the right answer for those who want to do it. – navark Sep 08 '16 at 19:02
  • @navark: I have added a simplified version at the bottom of my answer. – jxh Sep 08 '16 at 19:48
  • Why would you have both a unique_ptr and a shared_ptr in the class? The only incremental cost of a shared_ptr over a normal pointer is on creation, so there's no reason to not just create it up front -- don't complicate your code by converting back and forth. If it's ever going to be a shared_ptr just make a shared_ptr. And from the perspective of understanding the ownership, you could never treat this like a unique_ptr, you'd always have to assume it's using the shared_ptr version from a static analysis perspective. You're really just adding complex failure cases for virtually no benefit – xaxxon Sep 09 '16 at 06:46
  • @xaxxon The answer is based on Herb Sutter's article referenced in the question. The article argued `unique_ptr` is more efficient than `shared_ptr`. Therefore if most of the time the underlying type only needed uniqueness, it was superior to keep it that way. – jxh Sep 09 '16 at 06:52
  • more efficient, yes. Significantly more efficient when dealing with a non-massive amount of pointer creation? unlikely. Enough to add this level of complexity to your application? almost certainly not unless you have profile data to support it. Remember, dereferencing unique and shared ptr's is the same as a dumb pointer. – xaxxon Sep 09 '16 at 06:53
  • @xaxxon And so this answer clearly states that the question is really regarding how to implement an optimization. Then the answer illustrates how. – jxh Sep 09 '16 at 06:56
  • > First, use the simplest semantics that are sufficient: this is definitely not that. "Second, a unique_ptr is more efficient than a shared_ptr." conversion has a runtime cost as well as a code complexity cost -- adding complexity without profiling data is almost always a mistake. "starting with unique_ptr is more flexible and keeps your options open." I don't get that at all. If you make a shared ptr and never make a copy of it, it is essentially a *very* slightly worse unique_ptr. There is no flexibility lost. – xaxxon Sep 09 '16 at 06:57
  • "Then the answer illustrates how" fair enough, but it's important to understand why it might be considered bad. – xaxxon Sep 09 '16 at 06:57
  • @xaxxon I choose to believe a conscientious programmer will choose a complex optimization only if performance metrics indicated it was necessary. – jxh Sep 09 '16 at 07:02
  • @jxh the level of sophistication present in the question doesn't lend credence to the belief that this is being done with a real understanding of the performance implications. "I'm not fond of the idea of passing shared pointers everywhere in my code when I don't necessarily need shared ownership" shows a clear lack of understanding of how to pass parameters in a system relying on smart pointers to denote ownership. Also, the end of the question says: "If someone has a better idea of how to deal with situations like this I'd be glad to hear it" -- so I suggest "just use a shared pointer" – xaxxon Sep 09 '16 at 07:16
  • @xaxxon I try not to judge the quality of the programmer by the quality of the question. Also, future readers looking for an answer may have a real need. – jxh Sep 09 '16 at 07:28
1

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.

Altainia
  • 1,347
  • 1
  • 7
  • 11
  • 1
    You may simply do `srh.ptr = std::move(urh.ptr);`. – Jarod42 Sep 08 '16 at 17:51
  • This is not exactly what I wanted to do but I think you are correct in that replacing unique_ptr with shared_ptr when it's needed is trivial and may be the best solution – navark Sep 08 '16 at 18:46
0

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.

Gerhard Stein
  • 1,543
  • 13
  • 25
  • Are you sure that's how unique pointers are supposed to be used because my understanding was that the "uniqueness" was in the ownership of it but to refer to it you could pass raw pointers (or referenced unique_ptr) to that unique_ptr as much as you like – navark Sep 08 '16 at 18:04
  • Not sure, aren't we talking about the same? The "move" is not called "change", but still it's a pointer after all. I imagine it like an object/pointer moved through the program, maybe my own phantasy. If you pass raw pointers you are "sharing" without using the advantage of these classes really. I think getting raw pointers is more of an option, because you also have other ways to get this pointer anyways. You are using template classes after all. – Gerhard Stein Sep 08 '16 at 18:28
  • unique_ptr is not slower than raw pointers, as long as you have at least a little optimization enabled. – aschepler Sep 08 '16 at 18:46
  • Depends in how much can be optimized: http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c – Gerhard Stein Sep 08 '16 at 18:50
  • @Gerstrong && is many things but is *NEVER* a "move operator" – xaxxon Sep 08 '16 at 19:23
  • @xaxxon : Ok, let me fix that. – Gerhard Stein Sep 09 '16 at 04:45
  • 1
    @Gerstrong the name for that operator overload is "move assignment operator" -- just as there is a move constructor -- it's an important distinction because there is no operator that causes an object to be moved. It's subtle, but move semantics in c++ are difficult for many to understand, so keeping the terminology consistent is helpful. http://en.cppreference.com/w/cpp/language/move_assignment – xaxxon Sep 09 '16 at 06:42