What kind of smart pointer to choose?

Asked

Viewed 540 times

10

How to know what kind of smart pointer to use?

And what’s the difference between them?

Example:

std::unique_ptr<MinhaClasse> mClasse(new MinhaClasse)
std::shared_ptr<MinhaClasse> mClasse(new MinhaClasse)
std::auto_ptr<MinhaClasse> mClasse(new MinhaClasse)

3 answers

14


The auto_ptr was marked as obsolete in C++11 and removed in C++17.

unique_ptr and shared_ptr are complementary.

The unique_ptr only allows one pointer at a time to point to the administered resource (i.e., you cannot copy the pointer):

unique_ptr<T> myPtr(new MinhaClasse); // Ok
unique_ptr<T> myOtherPtr = myPtr; // Erro: Não pode copiar um unique_ptr

Already one shared_ptr can be shared, internally it uses a reference counter and only destroys the resource when the last pointer is destroyed.

shared_ptr<T> myPtr(new MinhaClasse); // Ok
shared_ptr<T> myOtherPtr = myPtr; // Dois ponteiros para o mesmo recurso

Source: Soen - Differences between unique_ptr and shared_ptr [Uplicate]

6

What’s the difference between them?

Each of the smart pointers provides a variation of the ownership rules of the resource being manipulated. For example, an intelligent pointer that has ownership without sharing manipulates the resource in a way that no other intelligent pointer can have shared ownership of the same resource. Smart hands with shared ownership allow more than one smart pointer to handle the same feature at the same time.

Note: I will omit the advantages of a smart pointer over a primitive pointer, since this discussion does not fit this thread.

Come on the differences:

1. std::unique_ptr: Sole possession

A Std::unique_ptr manages a resource through a pointer and discards that resource when the std::unique_ptr falls out of scope (or when .reset() is called.)

That one smart Pointer implements the rules of sole possession. That is, it is not possible to copy a std::unique_ptr, thus ensuring that the resource remains unique to the std::unique_ptr original. You can, however, transfer possession from that resource to another std::unique_ptr, leaving the original smart Pointer empty (ie without a resource to manage.)

For example:

auto p1 = std::make_unique<int>(42); // `std::make_unique` é C++14.
//std::unique_ptr<int> p2 = p1; // Errado: não é possível copiar um `std::unique_ptr`.
std::unique_ptr<int> p3 = std::move(p1); // Ok: posse do recurso foi transferida.

In the above example, the variable p1 is a std::unique_ptr<int>, that manages a pointer of int for a dynamic memory.

In the second line (commented), try to copy a std::unique_ptr is invalid and the compiler will complain (i.e. the code will not compile). This is purposeful: the copy-constructor of std::unique_ptr is implicitly declared as deleted.

On the third line, instead of a copy, we have the transfer of possession of the pointer. By calling Std: with the variable p1, we’re saying that " p1 can be moved, ie your resources can be transferred". This is important because it is now the move-constructor that will be called. It will copy the pointer managed by p1 and then cancel it. The following code demonstrates this effect:

assert(p3.get() != nullptr); // `p3` toma posse do recurso gerenciado por `p1`.
assert(*p3 == 42);
assert(p1.get() == nullptr); // `p1` agora não gerencia recurso algum.

2. std::shared_ptr: Shared possession

A Std::shared_ptr also manages a resource through a pointer, except that instances of std::shared_ptr can be copied. We call this shared ownership. This smart Pointer only discards the resource if it is the only one in possession of it.

When a copy of this smart Pointer happens, a reference counter (i.e. the number of smart pointers sharing the same resource) is incremented. When one of the smart pointers goes out of scope, or .reset() is called in it, this counter is decremented, but the resource remains alive. The resource only dies when the count of shared possessions reaches zero.

For example:

{
  auto s1 = std::make_shared<int>(42);

  {
    // Ok: a cópia significa compartilhamento do mesmo recurso. Contador incrementa.
    std::shared_ptr<int> s2 = s1;
  } // s2 sai de escopo e o contador decrementa.

} // s1 sai de escopo, contador decrementa e descarta o recurso.

A feature that std::shared_ptr has to be able to share the same resource, but point to a totally different place. This is the aliasing constructor. It allows us to build a std::shared_ptr who shares possession with one another std::shared_ptr, but having a different pointer value:

struct S
{
  int i;
  float f;
  S(int i, float f) : i(i), f(f) {}
};

auto s = std::make_shared<S>(42, 3.14f);

// Esses dois `shared_ptr`s seguintes compartilham o mesmo
// recurso do `s`, só que eles apontam pra lugares diferentes.
std::shared_ptr<int> si(s, &s->i);
std::shared_ptr<float> sf(s, &s->f);

// Ao de-referenciá-los, acessamos os membros do `s`.
assert(*si == 42);
assert(*sf == 3.14f);

As possession is still of the same resource, the reference counter works the same. That is, this S will only be discarded when all std:shared_ptronly to release the possession.

3. std::weak_ptr: Weak shared ownership

We still have the smart pointer Std::weak_ptr. This one goes hand in hand with the std::shared_ptr. A std::weak_ptr is a smart Pointer that maintains a reference unoccupied of a resource managed by a std::shared_ptr.

The idea of std::weak_ptr is to implement a temporary possession of the resource of a std::shared_ptr. He observes this resource without interfering in his life. To access it, this std::weak_ptr is converted to a std::shared_ptr to have a temporary possession of the resource. This is useful for when you want to access a resource only if it is available.

4. std::observer_ptr: No possession (library Fundamentals TS v2)

The pointer donkey Std::observer_ptr holds a primitive pointer with no possession whatsoever of this pointer resource. It is only an observer and is not responsible for resource management.

std::observer_ptr does not exist in any previous revision or in C++17. It is part of the proposal library Fundamentals TS v2 and maybe can be added in C++20.

How to know what kind of smart pointer to use?

When you have a feature and only want it to be automatically managed (i.e. there is no other non-trivial requirement and just want to have your lifespan automatically manipulated), std::unique_ptr then it’s enough. Most cases fit here. And actually use new and delete in normal codes is discouraged, it is recommended to use std::unique_ptr in place.

For features with special needs, such as features used across multiple threads, then std::shared_ptr will do very well, since its main purpose is to make it safe to share resources between competing codes. Remember: std::shared_ptr has a cost for this safety. Your reference counter is atomic1, making copies more expensive (slow). If you need to share ownership of a resource in a non-competitive or unparalleled code, I advise sticking to other smart pointers.

Already std::weak_ptr is useful to prevent circular references between std::shared_ptrs. There may be cases where a class Foo may be in possession of a class Bar, and at the same time Bar have possession over Foo. In this situation, one keeps the other alive, the counter never reaches zero and no one is discarded in the end. Then memory leakage happens, since the resources are not released. A std::weak_ptr breaks this cycle, since you only have possession of the resource after accessing it (and this possession is temporary).

A pointer like std::observer_ptr has no apparent use at first sight. However, its sole purpose is to be an alternative to primitive pointers that are used for the same purpose: just to observe a resource without having any ownership or responsibility over it. It is a vocabulary type and serves only to show the pointer intention without having to analyze the code in depth.

We have yet another smart pointer Std::atomic_shared_ptr being proposed for C++ (Concurrency TS). A std::atomic_shared_ptr is just one std::shared_ptr with a better interface than free functions to perform atomic operations with the asset in possession.


1 Depending on the implementation, the operation of incrementing/decreasing the counter is not atomic if the program does not use threads (i.e. does not compile with -pthread). But don’t assume that this is the case for all implementations.

4

Complementing Anthony Accioly’s answer that already answers the question, note that the fact that the pointers are smart does not mean that it does not need to have some coordination of its use. C++, unlike Rusto, for example, does not control the lifetime of the object at the time of compilation and if you call a function that kills an object and try to use it after calling that function you may have problems. You still need to have control of the object life time, just don’t need to worry as much as with raw pointers. It’s a lot harder to make a mistake by accident.

The unique_ptr is always preferred because it has zero abstraction, ie it does not consume memory, nor processing time. Only when you really need to share the object should you choose shared_ptr which has cost of memory and processing, weighed if in concurrent use. It is possible to transform an object unique_ptr in shared_ptr, otherwise with limitation.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.