When to use shared_ptr and when to use raw pointers?

C++Smart Pointers

C++ Problem Overview


class B;

class A
{
public:
    A ()
        : m_b(new B())
    {
    }

    shared_ptr<B> GimmeB ()
    {
        return m_b;
    }

private:
    shared_ptr<B> m_b;
};

Let's say B is a class that semantically should not exist outside of the lifetime of A, i.e., it makes absolutely no sense for B to exist by itself. Should GimmeB return a shared_ptr<B> or a B?*

In general, is it good practice to completely avoid using raw pointers in C++ code, in lieu of smart pointers?

I am of the opinion that shared_ptr should only be used when there is explicit transfer or sharing of ownership, which I think is quite rare outside of cases where a function allocates some memory, populates it with some data, and returns it, and there is understanding between the caller and the callee that the former is now "responsible" for that data.

C++ Solutions


Solution 1 - C++

Your analysis is quite correct, I think. In this situation, I also would return a bare B*, or even a [const] B& if the object is guaranteed to never be null.

Having had some time to peruse smart pointers, I arrived at some guidelines which tell me what to do in many cases:

  • If you return an object whose lifetime is to be managed by the caller, return std::unique_ptr. The caller can assign it to a std::shared_ptr if it wants.
  • Returning std::shared_ptr is actually quite rare, and when it makes sense, it is generally obvious: you indicate to the caller that it will prolong the lifetime of the pointed-to object beyond the lifetime of the object which was originally maintaining the resource. Returning shared pointers from factories is no exception: you must do this eg. when you use std::enable_shared_from_this.
  • You very rarely need std::weak_ptr, except when you want to make sense of the lock method. This has some uses, but they are rare. In your example, if the lifetime of the A object was not deterministic from the caller's point of view, this would have been something to consider.
  • If you return a reference to an existing object whose lifetime the caller cannot control, then return a bare pointer or a reference. By doing so, you tell the caller that an object exists and that she doesn't have to take care of its lifetime. You should return a reference if you don't make use of the nullptr value.

Solution 2 - C++

The question "when should I use shared_ptr and when should I use raw pointers?" has a very simple answer:

  • Use raw pointers when you do not want to have any ownership attached to the pointer. This job can also often be done with references. Raw pointers can also be used in some low level code (such as for implementing smart pointers, or implementing containers).
  • Use unique_ptr or scope_ptr when you want unique ownership of the object. This is the most useful option, and should be used in most cases. Unique ownership can also be expressed by simply creating an object directly, rather than using a pointer (this is even better than using a unique_ptr, if it can be done).
  • Use shared_ptr or intrusive_ptr when you want shared ownership of the pointer. This can be confusing and inefficient, and is often not a good option. Shared ownership can be useful in some complex designs, but should be avoided in general, because it leads to code which is hard to understand.

shared_ptrs perform a totally different task from raw pointers, and neither shared_ptrs nor raw pointers are the best option for the majority of code.

Solution 3 - C++

The following is a good rule of thumb:

  • When there is no transfer of shared ownership references or plain pointers are good enough. (Plain pointers are more flexible than references.)
  • When there is transfer of ownership but no shared ownership then std::unique_ptr<> is a good choice. Often the case with factory functions.
  • When there is shared ownership, then it is a good use case for std::shared_ptr<> or boost::intrusive_ptr<>.

It is best to avoid shared ownership, partly because they are most expensive in terms of copying and std::shared_ptr<> takes double of the storage of a plain pointer, but, most importantly, because they are conducive for poor designs where there are no clear owners, which, in turn, leads to a hairball of objects that cannot destroy because they hold shared pointers to each other.

The best design is where clear ownership is established and is hierarchical, so that, ideally, no smart pointers are required at all. For example, if there is a factory that creates unique objects or returns existing ones, it makes sense for the factory to own the objects it creates and just keep them by value in an associative container (such as std::unordered_map), so that it can return plain pointers or references to its users. This factory must have lifetime that starts before its first user and ends after its last user (the hierarchical property), so that users cannot possible have a pointer to an already destroyed object.

Solution 4 - C++

If you don't want the callee of GimmeB() to be able to extend the lifetime of the pointer by keeping a copy of the ptr after the instance of A dies, then you definitely should not return a shared_ptr.

If the callee is not supposed to keep the returned pointer for long periods of time, i.e. there's no risk of the instance of A's lifetime expiring before the pointer's, then raw pointer would be better. But even a better choice is simply to use a reference, unless there's a good reason to use an actual raw pointer.

And finally in the case that the returned pointer can exist after the lifetime of the A instance has expired, but you don't want the pointer itself extend the lifetime of the B, then you can return a weak_ptr, which you can use to test whether it still exists.

The bottom line is that there's usually a nicer solution than using a raw pointer.

Solution 5 - C++

I agree with your opinion that shared_ptr is best used when explicit sharing of resources occurs, however there are other types of smart pointers available.

In your precise case: why not return a reference ?

A pointer suggests that the data might be null, however here there will always be a B in your A, thus it will never be null. The reference asserts this behavior.

That being said, I have seen people advocating the use of shared_ptr even in non-shared environments, and giving weak_ptr handles, with the idea of "securing" the application and avoiding stale pointers. Unfortunately, since you can recover a shared_ptr from the weak_ptr (and it is the only way to actually manipulate the data), this is still shared ownership even if it was not meant to be.

Note: there is a subtle bug with shared_ptr, a copy of A will share the same B as the original by default, unless you explicitly write a copy constructor and a copy assignment operator. And of course you would not use a raw pointer in A to hold a B, would you :) ?


Of course, another question is whether you actually need to do so. One of the tenets of good design is encapsulation. To achieve encapsulation:

> You shall not return handles to your internals (see Law of Demeter).

so perhaps the real answer to your question is that instead of giving away a reference or pointer to B, it should only be modified through A's interface.

Solution 6 - C++

Generally, I would avoid using raw pointers as far as possible since they have very ambiguous meaning - you might have to deallocate the pointee, but maybe not, and only human-read and -written documentation tells you what the case is. And documentation is always bad, outdated or misunderstood.

If ownership is an issue, use a smart pointer. If not, I'd use a reference if practicable.

Solution 7 - C++

  1. You allocate B at constuction of A.
  2. You say B shouldn't persist outside As lifetime.
    Both these point to B being a member of A and a just returning a reference accessor. Are you overengineering this?

Solution 8 - C++

I found that the C++ Core Guidelines give some very useful hints for this question:

To use raw pointer(T*) or smarter pointer depends on who owns the object (whose responsibility to release memory of the obj).

> own : > > smart pointer, owner > > not own: > > T*, T&, span<>

owner<>, span<> is defined in Microsoft GSL library

here is the rules of thumb:

  1. never use raw pointer(or not own types) to pass ownership

  2. smart pointer should only be used when ownership semantics are intended

  3. T* or owner designate a individual object(only)

  4. use vector/array/span for array

  5. To my undetstanding, shared_ptr is usually used when you don't know who will release the obj, for example, one obj is used by multi-thread

Solution 9 - C++

It is good practice to avoid using raw pointers, but you can not just replace everything with shared_ptr. In the example, users of your class will assume that it's ok to extend B's lifetime beyond that of A's, and may decide to hold the returned B object for some time for their own reasons. You should return a weak_ptr, or, if B absolutely cannot exist when A is destroyed, a reference to B or simply a raw pointer.

Solution 10 - C++

When you say: "Let's say B is a class that semantically should not exist outside of the lifetime of A"

This tells me B should logically not exist without A, but what about physically existing? If you can be sure no one will try using a *B after A dtors than perhaps a raw pointer will be fine. Otherwise a smarter pointer may be appropriate.

When clients have a direct pointer to A you have to trust they'll handle it appropriately; not try dtoring it etc.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTripShockView Question on Stackoverflow
Solution 1 - C++Alexandre C.View Answer on Stackoverflow
Solution 2 - C++MankarseView Answer on Stackoverflow
Solution 3 - C++Maxim EgorushkinView Answer on Stackoverflow
Solution 4 - C++reko_tView Answer on Stackoverflow
Solution 5 - C++Matthieu M.View Answer on Stackoverflow
Solution 6 - C++thitonView Answer on Stackoverflow
Solution 7 - C++RicibobView Answer on Stackoverflow
Solution 8 - C++caminoView Answer on Stackoverflow
Solution 9 - C++hamstergeneView Answer on Stackoverflow
Solution 10 - C++seandView Answer on Stackoverflow