59

In C++11, you can use a shared_ptr<> to establish an ownership relation with an object or variable and weak_ptr<> to safely reference that object in a non-owned way.

You can also use unique_ptr<> to establish an ownership relation with an object or variable. But what if other, non-owning objects want to also reference that object? weak_ptr<> isn't helpful in this case. Raw pointers are helpful but bring various downsides (e.g. they can be automatically initialized to nullptr but this is accomplished through techniques that are not consistent with the std::*_ptr<> types).

What is the equivalent of weak_ptr<> for non-owning references to objects owned via unique_ptr<>?

Here's a clarifying example that resembles something in a game I'm working on.

class World
{
public:

    Trebuchet* trebuchet() const { return m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet* theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Duh. Oops. Dumb error. Nice if the compiler helped prevent this.
    }

private:

    Trebuchet* m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

Here we use a raw pointer to maintain a non-owning relationship with an object owned via unique_ptr<> elsewhere. But is raw the best we can do?

The hope is a type of pointer that:

  • Looks like the other modern pointer types. E.g. std::raw_ptr<T>.
  • Replaces raw pointers so that a codebase that uses modern pointer types throughout can find all pointers via a search for _ptr< (roughly).
  • Auto-initializes to nullptr.

Thus:

int* p;                  // Unknown value.
std::raw_ptr< int > p;   // null.

Does this type already exist in C++ now, is it proposed for the future, or is another implementation broadly available in e.g. Boost?

Community
  • 1
  • 1
OldPeculier
  • 11,049
  • 13
  • 50
  • 76
  • 11
    `unique_ptr::get` if you want access to the underlying pointer. There is no `weak_ptr` equivalent because then the `unique_ptr` wouldn't be very *unique* – Praetorian Jul 08 '13 at 22:03
  • 6
    There is nothing like that because `unique_ptr` is designed to have no overhead over a raw pointer. If it had to keep a refcount of all the weak pointers, that wouldn't be possible. – Xeo Jul 08 '13 at 22:06
  • 1
    @Xeo, that's fair. A `weak_ptr` equivalent for `unique_ptr` might have excessive cost. But mightn't there be some sort of intermediate functionality: a pointer type that is syntactically similar to the other modern pointer types, with automatically nullification on construction, prevention of direct calls to delete, etc., but with no additional overhead? These would presumably still be subject to dangling pointer problems, but would be no worse than—and indeed better than—raw pointers. Does any such type exist? – OldPeculier Jul 08 '13 at 22:09
  • @Old: How would you notify the nonowning pointers with zero overhead? – Billy ONeal Jul 08 '13 at 22:10
  • 2
    @OldPeculier You're confusing ownership semantics. `unique_ptr` is meant to be an owning pointer. If you want a pointer that others can have non-owning references to, use `shared_ptr`. What purpose would yet another smart pointer somewhere in between the two serve? – Praetorian Jul 08 '13 at 22:10
  • @BillyONeal You wouldn't. A `weak_ptr`-like non-owning pointer would be nice, but you're right: it would bring expense. But it's not the only option. See the clarifying example in the question, as well as my other comments for clarification.. – OldPeculier Jul 08 '13 at 22:21
  • 1
    @OldPeculier If you really want a smart pointer that behaves like a `unique_ptr`, but is non-owning, you can [cook one up](http://coliru.stacked-crooked.com/view?id=90b40a47ff42ebb68550f8092608c33a-793367a52c22d67c60e8f4d72a2f51e2) yourself by providing a deleter that doesn't do anything. However, I fail to see need for such a thing. A raw pointer is all that's needed instead of it. – Praetorian Jul 08 '13 at 22:22
  • @Praetorian I see your point. Perhaps I'm trying to have my cake and eat it too: avoid the overhead of `shared_ptr` and `weak_ptr` but provide for non-owning pointers. Still, you can't work long with `unique_ptr` before you want to offer limited non-owning pointers to the same object, and raw pointer feels, well, very raw for this purpose. – OldPeculier Jul 08 '13 at 22:23
  • @Praetorian No, there's no question about wanting `unique_ptr` to have ownership semantics. That would be nonsensical. The question is how to reference objects owned by a `unique_ptr` through other, non-owning pointers. Is raw the best we can do? That's the question. – OldPeculier Jul 08 '13 at 22:25
  • 2
    Your "Duh. Oops. Dumb error" is not very convincing. You should not be using `delete` outside out ownership handles (see also: [Rule of Zero](http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html)). Protecting against such a mistake is protecting against Machiavelli, not Murphy. – R. Martinho Fernandes Jul 08 '13 at 22:26
  • 1
    @OldPeculier besides "feeling raw" what is there to improve on a raw pointer? You cannot just ask for something better without explaining what counts as "better". – R. Martinho Fernandes Jul 08 '13 at 22:27
  • @R.MartinhoFernandes I work with many large and old code bases where modern pointer usage is being phased in. Sure, ideally you'd write everything from scratch and simply disallow delete and other sources of raw pointer pain. In practice, however, these paradigms coexist, and this kind of error is actually common. An alternative to raw pointer that initialized itself to nullptr and prevented deletion would avoid a pretty large set of errors. – OldPeculier Jul 08 '13 at 22:29
  • I don't see why that helps, simply because such code bases don't use such pointer already. I suppose you cannot simply replace every raw pointer with this new undeletable pointer (otherwise you could just grep for all instances of `delete` and get rid of them), so you still have to evaluate every single instance yourself. If you are doing such an effort, why not fix it for real and move the entire code to a modern style? – R. Martinho Fernandes Jul 08 '13 at 22:30
  • In any case, if you want a class that initializes itself to nullptr and preventes deletion, then no, there isn't one. You have to write it yourself. – R. Martinho Fernandes Jul 08 '13 at 22:37

9 Answers9

38

The "notify" behavior of shared_ptr requires reference counting the reference count control block. shared_ptr's reference count control block(s) use separate reference counts for this. weak_ptr instances maintain references to this block, and weak_ptrs themselves prevent the reference count control block from being deleteed. The pointed-to object has its destructor called when the strong count goes to zero (which may or may not result in deleteion of the memory where that object was stored), and the control block is deleteed only when the weak reference count goes to zero.

unique_ptr's tenet is that it has zero overhead over a plain pointer. Allocating and maintaining reference count control blocks (to support weak_ptr-ish semantics) breaks that tenet. If you need behavior of that description, then you really want shared semantics, even if other references to the object are non-owning. There's still sharing going on in that case -- the sharing of the state of whether or not the object has been destroyed.

If you need a generic nonowning reference and don't need notification, use plain pointers or plain references to the item in the unique_ptr.


EDIT:

In the case of your example, it looks like Victim should ask for a Trebuchet& rather than a Trebuchet*. Then it's clear who owns the object in question.

class World
{
public:

    Trebuchet& trebuchet() const { return *m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet& theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Compiler error. :)
    }

private:

    Trebuchet& m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 1
    The in-constructor initialization of a reference is fair given my example, but my example falls short in allowing this workaround. The more general case (and the case I'm actually dealing with in various circumstances in code) allows the pointer to be set after construction. – OldPeculier Jul 10 '13 at 01:57
  • 1
    @OldPeculier: I gave the "general answer" in the first part of my answer, and the specific one in the second part. – Billy ONeal Jul 10 '13 at 03:25
  • Fantastic advice to use the reference where possible. If you can design your "owner" container to be created first, then everything that "peeks" into it second, you can generally use references in the secondary containers and indexes, and everything is "just right". It can get tougher when there is a lot of change in the original container if it causes reallocations. Stick with [unordered_][set|map]'s! :-) – moodboom May 04 '15 at 17:55
  • This is a good explanation but unfortunately does not answer the question. There is actually a need to weak reference a pointer that cannot be copied or have multiple owners. The proposed non_owning_ptr<> will fixed that problem. – Jean-Simon Brochu Aug 28 '17 at 15:01
23

There is a genuine need for a standard pointer type to act as a non-owning, inexpensive, and well-behaved counterpoint to std::unique_ptr<>. No such pointer has been standardized yet, but a standard has been proposed and is under discussion by the C++ standards committee. The "World's Dumbest Smart Pointer", aka std::exempt_ptr<> would have the general semantics of other modern C++ pointer classes but would hold no responsibility either for owning the pointed-to object (as shared_ptr and unique_ptr do) or for correctly responding to the deletion of that object (as weak_ptr does).

Assuming that this feature is ultimately ratified by the committee, it would fully meet the need highlighted in this question. Even if it isn't ratified by the committee, the above linked document fully expresses the need and describes a complete solution.

OldPeculier
  • 11,049
  • 13
  • 50
  • 76
  • 2
    Note that the proposed `exempt_ptr` does not give you the notify semantics you were asking for. There's no standard pointer that does this, but it is trivial to write one. – Billy ONeal Jul 10 '13 at 03:26
  • 1
    @BillyONeal Please understand: I never asked for notify semantics. I implied it would be nice, but it's not intrinsic to the problem. – OldPeculier Jul 10 '13 at 15:56
  • 1
    v3 of the proposal is available here: [http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf](http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf) `exempt_ptr` has been renamed to `observer_ptr` – ChetS Jan 27 '14 at 19:36
  • 1
    I like it, if nothing else because it leads to better-documented code. It's similar in that way to using static_cast<> instead of hard casting during those nasty times when you have to do it - at the very least, it lets me search for the places where I know I misbehaved. :-) – moodboom May 04 '15 at 17:52
  • @user1754322 `weak_ptr` has the capacity to "be notified" when the object it points to is destroyed. I think that's the "notify semantics" that @Billy ONeal was referencing. – OldPeculier Mar 26 '18 at 13:21
8

unique_ptr's non-owing analog is a plain C pointer. What is different - C pointer doesn't know if the pointed data is still accessible. weak_ptr on the other hand does. But it is impossible to replace raw pointer with a pointer knowing about the validity of data without additional overhead (and weak_ptr does have that overhead). That implies C-style pointer is the best in terms of speed you can get as a non-owing analog for unique_ptr.

sasha.sochka
  • 14,395
  • 10
  • 44
  • 68
7

While you can't get a "weak" pointer to a uniquely owned object for free, the concept is useful and is used in a couple systems. See Chromium's WeakPtr and QT's QPointer for implementations.

Chromium's WeakPtr is implemented intrusively by storing a shared_ptr inside the weak-referenceable object and marking it invalid when the object is destroyed. WeakPtrs then reference that ControlBlock and check whether it's valid before handing out their raw pointer. I assume QT's QPointer is implemented similarly. Because ownership isn't shared, the original object is destroyed deterministically.

However, this means that dereferencing the WeakUniquePtr isn't thread-safe:

Thread 1:

unique_ptr<MyObject> obj(new MyObject);
thread2.send(obj->AsWeakPtr());
...
obj.reset();  // A

Thread2:

void receive(WeakUniquePtr<MyObject> weak_obj) {
  if (MyObject* obj = weak_obj.get()) {
    // B
    obj->use();
  }
}

If line A happens to run concurrently with line B, thread 2 will wind up using a dangling pointer. std::weak_ptr would prevent this problem by atomically taking a shared owning reference to the object before letting thread 2 use it, but that violates the assumption above that the object is owned uniquely. That means that any use of a WeakUniquePtr needs to be synchronized with the destruction of the real object, and the simplest way to do that is to require that they're done in a message loop on the same thread. (Note that it's still completely safe to copy the WeakUniquePtr back and forth across threads before using it.)

One could imagine using a custom deleter in std::unique_ptr to implement this using standard library types, but that's left as an exercise for the reader.

Jeffrey Yasskin
  • 5,171
  • 2
  • 27
  • 39
3
boost::optional<Trebuchet&>

As Billy ONeal pointed out in his answer you likely want to pass a Trebuchet& instead of a pointer. The problem with the reference is that you cannot pass a nullptr, boost::optional provides a way to have the equivilent of a nullptr. Further details on boost::optional are here: http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.html

See also this question: boost::optional<T&> vs T*

Note: std::optional<T> is on track to make it into C++14 but std::optional<T&> is a separate proposal that is not in the current C++14 draft. Further details here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html

Community
  • 1
  • 1
ChetS
  • 658
  • 7
  • 15
1

In the new C++ world with shared_ptr, weak_ptr, and unique_ptr you should not be storing long lived references to objects, like your trebuchet, using raw pointers or references. Instead World should have a shared_ptr to the trebuchet and Victim should store either a shared_ptr or a weak_ptr, depending on whether the trebuchet should stick around with the victim if the world goes away. Using a weak_ptr allows you to tell if the pointer is still valid (i.e. the world still exists), there is no way to do this with a raw pointer or reference.

When you use a unique_ptr you are declaring that only the World instance will own the trebuchet. Clients of the World class can use the World object's trebuchet by calling the "get" method but should not hold on to the reference or pointer returned by the method when they are done using it. Instead they should "borrow" the trebuchet every time they want to use it by calling the "get" method.

The above being said there could be instances where you want to store a reference or raw pointer for future use to avoid the overhead of the shared_ptr. But those instances are few and far between and you need to be completely sure that you won't use the pointer or reference after the World object that owns the trebuchet has gone away.

Brett Hall
  • 913
  • 7
  • 12
  • I don't know if all uses are temporary "borrows" outside of the primary unique_ptr - consider the case of needing to create multiple secondary indexes into a master container of unique_ptr's. – moodboom May 04 '15 at 18:28
  • That seems like a valid member of the "few and far between" category to me. – Brett Hall May 05 '15 at 20:12
  • You don't maintain large object sets? If you do, you are almost assuredly going to need multiple indexes into them. I guess it's a classic case of YMMV. – moodboom May 07 '15 at 18:12
  • Yeah, I'm agreeing with you. Having secondary indexes into an array of unique_ptrs seems like a valid use of raw pointers to me. "few and far between" was probably the wrong description to use, see http://bit.ly/1F4jEQK for my feeling on bare pointers in more detail. – Brett Hall May 08 '15 at 18:27
  • We're definitely thinking in the same direction. I'm working through this now with an unordered_multiset index into an unordered_set of unique_ptr's. I'm finding it fairly easy to use a multiset of raw pointers (which I will never allocate or deallocate, that's unique_ptr's job). But ideally I would use a const ref to the unique_ptr. It's difficult to set up hash and equals functions for that... but I haven't given up yet. I'm thinking I'll open another question on it. – moodboom May 08 '15 at 19:45
  • [Here it is...](http://stackoverflow.com/questions/30132951/create-multiple-indexes-into-a-large-collection-of-objects-with-smart-pointers) – moodboom May 08 '15 at 21:54
1

otn::raw::weak (from C++ Object Token Library) is non-owning, inexpensive, and well-behaved counterpoint to std::unique_ptr. Also in the library there is otn::safe::unique, a unique owner which can "notify" a non-owning otn::safe::weak about the deletion of the object.

#include <otn/all.hpp>
#include <iostream>

int main()
{
    using namespace std;
    using namespace otn;

    raw::weak_optional<int> raw_weak;
    if (!raw_weak)
        cout << "raw_weak is empty" << endl;

    cout << "--- create object in std_unique..." << endl;
    auto std_unique = std::make_unique<int>(42);
    raw_weak = std_unique;
    if (std_unique)
        cout << "std_unique is not empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty" << endl;

    cout << "--- move std_unique to safe_unique..." << endl;
    safe::unique_optional<int> safe_unique = std::move(std_unique);

    if (!std_unique)
        cout << "std_unique is empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is observs safe_unique" << endl;

    safe::weak_optional<int> safe_weak = safe_unique;
    if (safe_unique)
        cout << "safe_unique is not empty" << endl;
    if (!safe_weak.expired())
        cout << "safe_weak is not expired" << endl;

    cout << "--- destroy object in safe_unique..." << endl;
    utilize(std::move(safe_unique));
    if (!safe_unique)
        cout << "safe_unique is empty" << endl;
    if (safe_weak.expired())
        cout << "safe_weak is expired, it is not dangling" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is dangling!!!" << endl;
}

Output:

raw_weak is empty
--- create object in std_unique...
std_unique is not empty
raw_weak is not empty
--- move std_unique to safe_unique...
std_unique is empty
raw_weak is not empty, it is observs safe_unique
safe_unique is not empty
safe_weak is not expired
--- destroy object in safe_unique...
safe_unique is empty
safe_weak is expired, it is not dangling
raw_weak is not empty, it is dangling!!!
ViTech
  • 11
  • 2
0

A function taking a raw pointer or reference implicitly promises not to hold on to a copy of that pointer after the function has returned. In return the caller promises that the pointer is valid (or nullptr) until the callee has returned.

If you want to hold on to the pointer, you are sharing it (and should use shared_ptr). A unique_ptr manages a single copy of the pointer. You use raw pointers (or references) to refer to call functions involving that object.

This is the same for shared_ptr objects. weak_ptr only comes into play when you want to have an additional reference to the pointed too object that outlives the involved function. The main purpose of weak_ptr is to break reference cycles where two objects hold references to each other (and are therefore never released).

Remember however that taking shared_ptr or weak_ptr implies that the function taking that parameter will (optionally) modify some other object to retain a reference to the pointed to object that outlives the invocation of the function. In the vast majority of cases you use raw pointer (if nullptr is a valid value) or ref (when a value is guaranteed) even for shared_ptr or weak_ptr.

Paul de Vrieze
  • 4,888
  • 1
  • 24
  • 29
0

now you hava noshared_ptr --

https://github.com/xhawk18/noshared_ptr

noshared_ptr<T> -- a new type of unique ptr
noweak_ptr<T>   -- weak ptr for the noshared_ptr
xhawk18
  • 169
  • 1
  • 4