4

In modern C++ how should I manage unowned pointers? I was thinking something like a weak_ptr for unique_ptr, but that doesn't seem to exist.

Example

For instance, if I have a class A that owns a pointer, I should use unique_ptr<X> instead of an old X* pointer, e.g.

class A
{
    std::unique_ptr<X> _myX;
};

But then if I have another class B that uses this pointer, what do I do here? With C style pointers I would do this:

class B
{
    X* _someX;
};

That seems correct, but it isn't obvious from the code that I've referenced another object's pointer (for instance a reader may think I could have just not used a smart pointer).

I have considered the following

  • std::shared_ptr<X> - seems like a waste of reference counting because A is guaranteed to outlive B.
  • std::weak_ptr<X> - only works with shared_ptr
  • X& - only works if X& is available in B's constructor

This seems like an obvious issue and sorry if it's been asked before. I've looked around and I have seen this question, but unfortunately the OP asked "is it OK to use X*" in one specific case. I'm looking for what I should generally be doing instead of X*, (if anything!).

c z
  • 7,726
  • 3
  • 46
  • 59
  • 13
    A raw pointer inherently means you have a non-owning pointer, so there is nothing to add or do. In other words let class `B` carry a raw pointer, which expresses that you have no hand in managing the memory of that object. – Cory Kramer Aug 18 '21 at 18:01
  • Not a duplicate, but a related historical curiosity: https://stackoverflow.com/questions/31862412/use-of-observer-ptr – Drew Dormann Aug 18 '21 at 18:03
  • You're going to find opinions around this, but the other main option people use is a different spelling for the same thing purely to convey intent (such as `observer_ptr`, though some things use an alias and some use a separate type). – chris Aug 18 '21 at 18:03
  • manage ownership, not pointers – C.M. Aug 18 '21 at 21:13

4 Answers4

9

Bjarne Stroustrup has weighed in on this matter.

Pointers are really good at pointing to "things" and T* is a really good notation for that... What pointers are not good at is representing ownership and directly support safe iteration.

He makes two suggestions:

  • Consider T* to mean "non-owning pointer to T" in modern C++
  • Use template<typename T> using observer_ptr = T*; if you desire your variable declarations to explicitly document this non-ownership.
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • There are considerations for template class for `observer_ptr` instead of an alias to raw pointer. As this changes type as well which has purpose. – ALX23z Aug 18 '21 at 18:29
  • This is a bad implementation of `observer_ptr`. Experimental has a better one: https://en.cppreference.com/w/cpp/experimental/observer_ptr – SergeyA Aug 18 '21 at 18:30
  • 1
    @SergeyA "better" is debatable - that's the same `observer_ptr` Stroustrup spends 3 pages lambasting in the document, concluding by suggesting the alternative version posted by Drew. – c z May 02 '23 at 11:38
2

That seems correct, but it isn't obvious from the code that I've referenced another object's pointer

Ideally, it should be obvious. If the pointer owned the resource, then it should have been a smart pointer. Since it isn't a smart pointer, that implies that it shouldn't own the resource.

That said, in the reality that has C / ancient C++ / badly designed interfaces which use owning pointers, you can clarify the lack of ownership like this:

class B
{
    X* _someX; // no ownership
};

It is also possible to define a custom class template wrapper for this purpose, and there has been a proposal to include such template in the standard library, which was adopted as a technical specification, but has not been adopted into the standard proper. To my understanding, there's no consensus on whether such wrapper is useful or unnecessary.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

Your question is largely opinion based, but we can take a look at what the core guideline has to say about it:

R.3: A raw pointer (a T*) is non-owning

Reason

There is nothing (in the C++ standard or in most code) to say otherwise and most raw pointers are non-owning. We want owning pointers identified so that we can reliably and efficiently delete the objects pointed to by owning pointers.

The guideline promotes to never use a raw pointer when it is an owning pointer. Once you do that, a raw pointer also readily signals that it is non-owning. The downside of this interpretation is:

Exception

A major class of exception is legacy code, especially code that must remain compilable as C or interface with C and C-style C++ through ABIs. The fact that there are billions of lines of code that violate this rule against owning T*s cannot be ignored. [...]

Not all raw pointers are non-owning, and often we cannot do much about that. However, if your code is written after C++11, then using a raw pointer should be sufficient to signal that it is not an owning pointer.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

https://abseil.io/tips/116 is a good discussion of the options here. If you have the thing at construction time, and never need to repoint it, then T& is probably good. If you don't, then "hey this might be null" is part of reality for that pointer, and T* is fine - a "raw" pointer in modern C++ generally communicates an optional, non-owning reference.

Alec Story
  • 11
  • 1