- géniaux
- puissants
- et pourtant…
la 1ère source de bugs!
new
et delete
sont sur un bateau…
MyType* mytype_pointer = new MyType;
...
delete mytype_pointer;
… delete
tombe à l’eau!
Qu’est-ce qui reste?
- Oublier ‘delete’ = perte mémoire
(catastrophe sur un système avec peu de ressources) delete
sur un nullptr = crashdelete
sur un pointeur invalide = crashdelete
de trop = crashdelete
sur le mauvais objet = c’est la 4ième dimension!
Une paire indissociable
(adapté au C++)
Ressource Acquisition Is Initialization
TL;DR: Associer la vie d’une ressource à celle d’un objet
class MyType_RAII {
public:
MyType_RAII() {
// Acquisition des ressources
...
}
~MyType_RAII() {
// Libération des ressources
...
}
};
{
MyType_RAII my_var; // allocation sur la pile
do_something(my_var);
} // ~MyType_RAII() appelé ici => libération des ressources
- éviter l’oublis de l’associativité (bug!)
- simplifier la maintenance du code
- simplifier la gestion des “cas de sortie” d’une routine
surtout avec les exceptions!
{
do_bad_things(new MyType); // perte si exception
MyType* ptr = new MyType; // alloué sur la stack
if (isError())
throw std::runtime_error("Error!"); // perte!
do_something_that_throw(); // perte!
} // perte!
class MyTypeSafePtr {
public:
MyTypeSafePtr() : ptr {new MyType} {}
~MyTypeSafePtr() { delete ptr; }
MyTypeSafePtr* ptr;
};
Utilisation
// Bon vieux C++98...
MyTypeSafePtr* my_ptr = MyTypeSafePtr();
// ...C++11
auto my_ptr = MyTypeSafePtr();
do_something(my_ptr.ptr);
my_ptr.ptr->do_something_else();
323
concept de propriété
- Posséder c’est décider de la durée de vie de l’objet
- J’utilise la ressource et je peux la prêter
- Je peux céder ma ressource à un autre propriétaire
Unicité
class MyTypeSafePtr {
private:
MyType* ptr_; // plus d'accès direct
public:
MyTypeSafePtr() ptr_ { new MyType } {}
~MyTypeSafePtr() { if (ptr_) delete ptr_; }
// accès au pointeur (/!\ pointeur nu = non-propriétaire)
MyType* get() const { return ptr_; }
// transfert de la propriété
MyType* release() noexcept {
auto t = ptr_;
ptr_ = nullptr;
return t;
}
};
Utilisation
auto my_ptr = MyTypeSafePtr();
// do_something(MyType* p)
do_something(my_ptr.get());
// take_ownership(MyType* p)
take_ownership(my_ptr.release());
// indirection
my_ptr.get().mytype_method();
Oublier l’effet conteneur
my_ptr.get()
=> pas très élégant- Mimer l’utilisation des pointeurs bruts
class MyTypeSafePtr {
...
// Déréférencement : *instance
MyType& operator* () const {
assert(get() != nullptr);
return *ptr_;
}
// Indirection: instance->xxx
MyTypr* operator-> () const noexcept { return ptr_; }
};
auto my_ptr = MyTypeSafePtr();
my_ptr->do_something(); // par indirection
foo(*my_ptr); // par réference
(Beaucoup mieux)
Programmation générique => template
template< typename T >
class MyUniquePtr {
public:
using pointer = T*;
MyUniquePtr(pointer p) : p_ {p} {}
~MyUniquePtr() { if (p_) delete _p; }
T& operator*() const { ... }
T* operator->() const noexcept { ... }
...
private:
pointer p_ {nullptr};
};
Utilisation
auto my_ptr = MyUniquePtr< MyType >(new MyType);
- <memory> de la STL (Standard Template Library)
- Depuis C++11
- Propriété unique sur un pointeur
- Comme notre exemple précédent mais en mieux
- Copie impossible (vérification à la compilation)
- Supporte la sémantique moveable du C++11
- C++14 =>
std::make_unique()
- Exemples d’utilisations:
- A la place d’un
new
nu (protection des fuites mémoire) - Comme membre de classe pour garder une ressource
- Parfait pour l’idiome pimpl
- A la place d’un
- A utiliser quand on ne sais pas qui est le responsable
- Plus complexe qu’un
std::unique_ptr
- besoin d’un compteur de référence
- la copie incrémente le compteur
- le dtor décrémente le compteur
- compteur à 0 => l’objet pointé est détruit (delete)
- Avoir l’accès à un
shared_ptr
sans augmenter le compteur de référence - Concept de “vue”
- Exemple: implémenter un cache
- On utilise la méthode
lock()
pour obtenir unshared_ptr
if (auto shared = weak.lock()) {
// shared bloque la destruction de la mémoire pointée
// et le scope de ce shared_ptr n'est valable qu'ici
}
Almost Zero-Overhead
- Aussi performant qu’un pointeur brut
- Son seul membre est un simple pointer
- Le compilateur ne gardera que la sémantique d’un pointeur simple
- … et il rajoute le
delete
là où vous l’auriezoubliémis
- Beaucoup moins performant que
std::unique_ptr
- Contient un compteur et un mutex
- Le mutex gére l’accès conccurentiel au compteur
- /!\ seul ce compteur est protégé, pas l’objet pointé!
- Référence sur la mémoire partagée
- Accéde aussi au mutex partagé entre les shared_ptr<>
Non! Tout comme la référence (&) il transporte une information utile
Il indique clairement que vous n’êtes pas le propriétaire
(pas de delete
dessus)
- Préférez &, ou * (pour indiquer une possible valeur à nullptr)
- Pas de smart pointer sauf si vous utilisez/modifiez ce dernier
Quelques exemples à ne pas suivre…
void method(const Widget& widget)
{
privateMember_ = std::unique_ptr< Widget >(&widget);
}
void foo(std::unique_ptr< Widget > w1,
std::unique_ptr< Widget > w2);
foo(new Widget1, new Widget2);
void foo(std::unique_ptr< Widget > w1,
std::unique_ptr< Widget > w2);
foo(std::unique_ptr< Widget >(new Widget),
std::unique_ptr< Widget >(new Widget));
void foo(std::unique_ptr< Widget > w1,
std::unique_ptr< Widget > w2);
foo(std::make_unique< Widget >(),
std::make_unique< Widget >());
Ordre d’utilisation des trois smart-pointers
type | quand? |
---|---|
/ | < |
unique_ptr | souvent, plus de new dit nu |
shared_ptr | partage de la propriété, avec précaution (1) |
weak_ptr | pour les cas particuliers (1), ex: cache |
(1) Le mutex est coûteux!
La référence
http://en.cppreference.com/w/cpp/memory
Ma source sur le sujet
https://herbsutter.com/gotw/
Source Images: giphy.com
Edité avec Emacs 25.2.1
Présentation: Org + reveal.js