C++
Pointeurs intelligents
Recherche…
Syntaxe
-
std::shared_ptr<ClassType> variableName = std::make_shared<ClassType>(arg1, arg2, ...);
-
std::shared_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
-
std::unique_ptr<ClassType> variableName = std::make_unique<ClassType>(arg1, arg2, ...);
// C ++ 14 -
std::unique_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
Remarques
C ++ n'est pas un langage géré par la mémoire. La mémoire allouée dynamiquement (c.-à-d. Les objets créés avec new
) sera "fuite" si elle n'est pas explicitement libérée (avec delete
). Il est de la responsabilité du programmeur de s'assurer que la mémoire allouée dynamiquement est libérée avant de supprimer le dernier pointeur sur cet objet.
Les pointeurs intelligents peuvent être utilisés pour gérer automatiquement la portée de la mémoire allouée dynamiquement (c.-à-d. Que lorsque la dernière référence du pointeur est hors de portée, elle est supprimée).
Les pointeurs intelligents sont préférés aux pointeurs "bruts" dans la plupart des cas. Ils expliquent la sémantique de propriété de la mémoire allouée dynamiquement en communiquant dans leurs noms si un objet doit être partagé ou possédé de manière unique.
Utilisez #include <memory>
pour pouvoir utiliser des pointeurs intelligents.
Partage de propriété (std :: shared_ptr)
Le modèle de classe std::shared_ptr
définit un pointeur partagé capable de partager la propriété d'un objet avec d'autres pointeurs partagés. Cela contraste avec std::unique_ptr
qui représente la propriété exclusive.
Le comportement de partage est implémenté via une technique appelée comptage de référence, dans laquelle le nombre de pointeurs partagés qui pointent vers l'objet est stocké à côté. Lorsque ce nombre atteint zéro, soit par la destruction ou la réaffectation de la dernière instance std::shared_ptr
, l'objet est automatiquement détruit.
// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);
Pour créer plusieurs pointeurs intelligents qui partagent le même objet, nous devons créer un autre shared_ptr
qui utilise le premier pointeur partagé. Voici 2 façons de le faire:
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
L'une des manières ci-dessus fait de secondShared
un pointeur partagé qui partage la propriété de notre instance de Foo
avec firstShared
.
Le pointeur intelligent fonctionne comme un pointeur brut. Cela signifie que vous pouvez utiliser *
pour les déréférencer. L'opérateur normal ->
fonctionne également:
secondShared->test(); // Calls Foo::test()
Enfin, lorsque le dernier alias shared_ptr
est hors de portée, le destructeur de notre instance Foo
est appelé.
Avertissement: la construction d'un shared_ptr
peut bad_alloc
une exception bad_alloc
lorsque des données supplémentaires pour la sémantique de propriété partagée doivent être allouées. Si le constructeur reçoit un pointeur normal, il suppose qu'il possède l'objet pointé et appelle le paramètre si une exception est levée. Cela signifie que shared_ptr<T>(new T(args))
ne fuira pas un objet T
si l'allocation de shared_ptr<T>
échoue. Cependant, il est conseillé d'utiliser make_shared<T>(args)
ou make_shared<T>(args)
allocate_shared<T>(alloc, args)
, ce qui permet à l'implémentation d'optimiser l'allocation de mémoire.
Allocation de tableaux ([]) à l'aide de shared_ptr
Malheureusement, il n'existe aucun moyen direct d'allouer des tableaux à l'aide de make_shared<>
.
Il est possible de créer des tableaux pour shared_ptr<>
utilisant new
et std::default_delete
.
Par exemple, pour allouer un tableau de 10 nombres entiers, nous pouvons écrire le code en tant que
shared_ptr<int> sh(new int[10], std::default_delete<int[]>());
Spécifier std::default_delete
est obligatoire ici pour vous assurer que la mémoire allouée est correctement nettoyée en utilisant delete[]
.
Si nous connaissons la taille au moment de la compilation, nous pouvons le faire de la manière suivante:
template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
std::shared_ptr<T> operator()const{
auto r = std::make_shared<std::array<T,N>>();
if (!r) return {};
return {r.data(), r};
}
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }
puis make_shared_array<int[10]>
retourne un shared_ptr<int>
pointant vers 10 ints tous construits par défaut.
Avec C ++ 17, shared_ptr
pris en charge les types de tableaux. Il n'est plus nécessaire de spécifier explicitement le tableau-deleter et le pointeur partagé peut être déréférencé à l'aide de l'opérateur d'index de tableau []
:
std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;
Les pointeurs partagés peuvent pointer vers un sous-objet de l'objet qui lui appartient:
struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);
p2
et p1
possèdent tous deux l'objet de type Foo
, mais p2
pointe vers son membre int
x
. Cela signifie que si p1
est hors de portée ou est réaffecté, l'objet Foo
sous-jacent sera toujours en vie, garantissant que p2
ne pendent pas.
Important: Un shared_ptr
ne connaît que lui-même et tous les autres shared_ptr
créés avec le constructeur alias. Il ne connaît aucun autre pointeur, y compris tous les autres shared_ptr
créés avec une référence à la même instance de Foo
:
Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this
shared1.reset(); // this will delete foo, since shared1
// was the only shared_ptr that owned it
shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
// deleted already!!
Transfert de propriété de shared_ptr
Par défaut, shared_ptr
incrémente le compte de référence et ne transfère pas la propriété. Cependant, il est possible de transférer la propriété en utilisant std::move
:
shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1
Partage avec propriété temporaire (std :: faiblesse_ptr)
Les instances de std::weak_ptr
peuvent pointer vers des objets appartenant à des instances de std::shared_ptr
tout en ne devenant que des propriétaires temporaires. Cela signifie que les pointeurs faibles ne modifient pas le compte de référence de l'objet et n'empêchent donc pas la suppression d'un objet si tous les pointeurs partagés de l'objet sont réaffectés ou détruits.
Dans l'exemple suivant, les instances de std::weak_ptr
sont utilisées pour std::weak_ptr
la destruction d'un objet d'arborescence:
#include <memory>
#include <vector>
struct TreeNode {
std::weak_ptr<TreeNode> parent;
std::vector< std::shared_ptr<TreeNode> > children;
};
int main() {
// Create a TreeNode to serve as the root/parent.
std::shared_ptr<TreeNode> root(new TreeNode);
// Give the parent 100 child nodes.
for (size_t i = 0; i < 100; ++i) {
std::shared_ptr<TreeNode> child(new TreeNode);
root->children.push_back(child);
child->parent = root;
}
// Reset the root shared pointer, destroying the root object, and
// subsequently its child nodes.
root.reset();
}
Au fur et à mesure que des nœuds enfants sont ajoutés aux enfants du nœud racine, leur parent
membre std::weak_ptr
est défini sur le nœud racine. Le parent
membre est déclaré comme un pointeur faible par opposition à un pointeur partagé, de sorte que le compte de référence du nœud racine n'est pas incrémenté. Lorsque le nœud racine est réinitialisé à la fin de main()
, la racine est détruite. Étant donné que les seules références std::shared_ptr
restantes aux nœuds enfants étaient contenues dans les children
collection root, tous les nœuds enfants sont également détruits par la suite.
En raison des détails de l'implémentation du bloc de contrôle, la mémoire allouée shared_ptr peut ne pas être libérée jusqu'à ce que le compteur de référence shared_ptr
et le weak_ptr
référence weak_ptr
atteignent tous deux zéro.
#include <memory>
int main()
{
{
std::weak_ptr<int> wk;
{
// std::make_shared is optimized by allocating only once
// while std::shared_ptr<int>(new int(42)) allocates twice.
// Drawback of std::make_shared is that control block is tied to our integer
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// sh memory should be released at this point...
}
// ... but wk is still alive and needs access to control block
}
// now memory is released (sh and wk)
}
Comme std::weak_ptr
ne conserve pas son objet référencé en vie, l'accès direct aux données via std::weak_ptr
n'est pas possible. Au lieu de cela, il fournit une fonction membre lock()
qui tente de récupérer un std::shared_ptr
vers l'objet référencé:
#include <cassert>
#include <memory>
int main()
{
{
std::weak_ptr<int> wk;
std::shared_ptr<int> sp;
{
std::shared_ptr<int> sh = std::make_shared<int>(42);
wk = sh;
// calling lock will create a shared_ptr to the object referenced by wk
sp = wk.lock();
// sh will be destroyed after this point, but sp is still alive
}
// sp still keeps the data alive.
// At this point we could even call lock() again
// to retrieve another shared_ptr to the same data from wk
assert(*sp == 42);
assert(!wk.expired());
// resetting sp will delete the data,
// as it is currently the last shared_ptr with ownership
sp.reset();
// attempting to lock wk now will return an empty shared_ptr,
// as the data has already been deleted
sp = wk.lock();
assert(!sp);
assert(wk.expired());
}
}
Propriété unique (std :: unique_ptr)
Un std::unique_ptr
est un modèle de classe qui gère la durée de vie d'un objet stocké dynamiquement. Contrairement à std::shared_ptr
, l'objet dynamique std::shared_ptr
qu'à une seule instance de std::unique_ptr
,
// Creates a dynamic int with value of 20 owned by a unique pointer
std::unique_ptr<int> ptr = std::make_unique<int>(20);
(Remarque: std::unique_ptr
est disponible depuis C ++ 11 et std::make_unique
depuis C ++ 14.)
Seule la variable ptr
contient un pointeur sur un int
alloué dynamiquement. Lorsqu'un pointeur unique possédant un objet est hors de portée, l'objet possédé est supprimé, c'est-à-dire que son destructeur est appelé si l'objet est de type classe et que la mémoire de cet objet est libérée.
Pour utiliser std::unique_ptr
et std::make_unique
avec des types de tableaux, utilisez leurs spécialisations de tableaux:
// Creates a unique_ptr to an int with value 59
std::unique_ptr<int> ptr = std::make_unique<int>(59);
// Creates a unique_ptr to an array of 15 ints
std::unique_ptr<int[]> ptr = std::make_unique<int[]>(15);
Vous pouvez accéder à std::unique_ptr
comme un pointeur brut, car il surcharge ces opérateurs.
Vous pouvez transférer la propriété du contenu d'un pointeur intelligent vers un autre pointeur en utilisant std::move
, ce qui entraînera le pointeur intelligent d'origine à pointer sur nullptr
.
// 1. std::unique_ptr
std::unique_ptr<int> ptr = std::make_unique<int>();
// Change value to 1
*ptr = 1;
// 2. std::unique_ptr (by moving 'ptr' to 'ptr2', 'ptr' doesn't own the object anymore)
std::unique_ptr<int> ptr2 = std::move(ptr);
int a = *ptr2; // 'a' is 1
int b = *ptr; // undefined behavior! 'ptr' is 'nullptr'
// (because of the move command above)
Passer unique_ptr
aux fonctions en paramètre:
void foo(std::unique_ptr<int> ptr)
{
// Your code goes here
}
std::unique_ptr<int> ptr = std::make_unique<int>(59);
foo(std::move(ptr))
Renvoyer unique_ptr
des fonctions. Il s’agit de la méthode C ++ 11 d’écriture des fonctions de fabrique préférée, car elle transmet clairement la sémantique de propriété du retour: l’appelant est propriétaire du unique_ptr
résultant et en est responsable.
std::unique_ptr<int> foo()
{
std::unique_ptr<int> ptr = std::make_unique<int>(59);
return ptr;
}
std::unique_ptr<int> ptr = foo();
Comparez ceci à:
int* foo_cpp03();
int* p = foo_cpp03(); // do I own p? do I have to delete it at some point?
// it's not readily apparent what the answer is.
Le modèle de classe make_unique
est fourni depuis C ++ 14. Il est facile de l'ajouter manuellement au code C ++ 11:
template<typename T, typename... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args&&... args)
{ return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
// Use make_unique for arrays
template<typename T>
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(size_t n)
{ return std::unique_ptr<T>(new typename std::remove_extent<T>::type[n]()); }
Contrairement au pointeur intelligent stupide ( std::auto_ptr
), unique_ptr
peut également être instancié avec l'allocation vectorielle ( pas std::vector
). Les exemples précédents concernaient les allocations scalaires . Par exemple, pour avoir un tableau entier alloué dynamiquement pour 10 éléments, vous devez spécifier int[]
comme type de modèle (et pas seulement int
):
std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(10);
Qui peut être simplifié avec:
auto arr_ptr = std::make_unique<int[]>(10);
Maintenant, vous utilisez arr_ptr
comme s'il s'agissait d'un tableau:
arr_ptr[2] = 10; // Modify third element
Vous ne devez pas vous soucier de la désaffectation. Cette version spécialisée du modèle appelle les constructeurs et les destructeurs de manière appropriée. Utiliser une version vectorisée de unique_ptr
ou un vector
lui vector
même - est un choix personnel.
Dans les versions antérieures à C ++ 11, std::auto_ptr
était disponible. Contrairement à unique_ptr
il est autorisé à copier auto_ptr
s, sur lequel le ptr
source perdra la propriété du pointeur contenu et la cible le recevra.
Utilisation de paramètres personnalisés pour créer un wrapper vers une interface C
De nombreuses interfaces C telles que SDL2 ont leurs propres fonctions de suppression. Cela signifie que vous ne pouvez pas utiliser directement les pointeurs intelligents:
std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!
Au lieu de cela, vous devez définir votre propre compteur. Les exemples utilisés ici utilisent la structure SDL_Surface
qui devrait être libérée à l'aide de la fonction SDL_FreeSurface()
, mais ils devraient pouvoir être adaptés à de nombreuses autres interfaces C.
Le deleter doit pouvoir être appelé avec un argument de pointeur et peut donc être, par exemple, un simple pointeur de fonction:
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
Tout autre objet appelable fonctionnera également, par exemple une classe avec un operator()
:
struct SurfaceDeleter {
void operator()(SDL_Surface* surf) {
SDL_FreeSurface(surf);
}
};
std::unique_ptr<SDL_Surface, SurfaceDeleter> a(pointer, SurfaceDeleter{}); // safe
std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // equivalent to the above
// as the deleter is value-initialized
Cela vous fournit non seulement une gestion de mémoire automatique sûre et sans surcharge (si vous utilisez unique_ptr
), vous obtenez également une sécurité d'exception.
Notez que le paramètre deleter fait partie du type de unique_ptr
, et que l'implémentation peut utiliser l' optimisation de la base vide pour éviter toute modification de la taille des paramètres personnalisés vides. Donc, bien que std::unique_ptr<SDL_Surface, SurfaceDeleter>
et std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)>
résolvent le même problème de la même manière, le premier type n’a toujours que la taille d’un pointeur ce dernier type doit contenir deux pointeurs: le SDL_Surface*
et le pointeur de fonction! Lorsque vous avez des fonctions personnalisées, il est préférable d’emballer la fonction dans un type vide.
Dans les cas où le comptage de références est important, on pourrait utiliser un shared_ptr
au lieu d'un unique_ptr
. Le shared_ptr
stocke toujours un deleter, cela efface le type du deleter, ce qui peut être utile dans les API. Les inconvénients shared_ptr
utilisation de shared_ptr
sur unique_ptr
comprennent un coût de mémoire plus élevé pour le stockage du compteur et un coût de performance pour le maintien du nombre de références.
// deleter required at construction time and is part of the type
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
// deleter is only required at construction time, not part of the type
std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface);
Avec le template auto
, nous pouvons encore plus facilement emballer nos filtres personnalisés:
template <auto DeleteFn>
struct FunctionDeleter {
template <class T>
void operator()(T* ptr) {
DeleteFn(ptr);
}
};
template <class T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;
Avec lequel l'exemple ci-dessus est simplement:
unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);
Ici, le but de l' auto
est de gérer toutes les fonctions libres, qu'elles renvoient des void
(par exemple SDL_FreeSurface
) ou non (par exemple, fclose
).
Propriété unique sans sémantique de déplacement (auto_ptr)
REMARQUE: std::auto_ptr
est obsolète dans C ++ 11 et sera supprimé dans C ++ 17. Vous ne devriez l'utiliser que si vous êtes obligé d'utiliser C ++ 03 ou une version antérieure et que vous êtes prêt à faire attention. Il est recommandé de passer à unique_ptr en combinaison avec std::move
pour remplacer le comportement std::auto_ptr
.
Avant d'avoir std::unique_ptr
, avant que nous ayons la sémantique de déplacement, nous avions std::auto_ptr
. std::auto_ptr
fournit une propriété unique mais transfère la propriété sur la copie.
Comme avec tous les pointeurs intelligents, std::auto_ptr
nettoie automatiquement les ressources (voir RAII ):
{
std::auto_ptr<int> p(new int(42));
std::cout << *p;
} // p is deleted here, no memory leaked
mais autorise un seul propriétaire:
std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px;
// px is now empty
Cela permet d'utiliser std :: auto_ptr pour garder la propriété explicite et unique au risque de perdre la propriété involontairement:
void f(std::auto_ptr<X> ) {
// assumes ownership of X
// deletes it at end of scope
};
std::auto_ptr<X> px = ...;
f(px); // f acquires ownership of underlying X
// px is now empty
px->foo(); // NPE!
// px.~auto_ptr() does NOT delete
Le transfert de propriété a eu lieu dans le constructeur "copy". Le constructeur de copie et l'opérateur d'assignation de copie auto_ptr
prennent leurs opérandes par référence non const
pour qu'ils puissent être modifiés. Un exemple de mise en œuvre pourrait être:
template <typename T>
class auto_ptr {
T* ptr;
public:
auto_ptr(auto_ptr& rhs)
: ptr(rhs.release())
{ }
auto_ptr& operator=(auto_ptr& rhs) {
reset(rhs.release());
return *this;
}
T* release() {
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
void reset(T* tmp = nullptr) {
if (ptr != tmp) {
delete ptr;
ptr = tmp;
}
}
/* other functions ... */
};
Cela rompt la sémantique de la copie, qui exige que la copie d'un objet vous laisse deux versions équivalentes. Pour tout type copiable, T
, je devrais pouvoir écrire:
T a = ...;
T b(a);
assert(b == a);
Mais pour auto_ptr
, ce n'est pas le cas. Par conséquent, il n'est pas prudent de mettre auto_ptr
s dans des conteneurs.
Obtenir un shared_ptr faisant référence à ceci
enable_shared_from_this
vous permet d'obtenir un valide shared_ptr
par exemple à this
.
En dérivant votre classe du modèle de classe enable_shared_from_this
, vous shared_from_this
une méthode shared_from_this
qui retourne une instance shared_ptr
à this
.
Notez que l'objet doit être créé en tant que shared_ptr
à la première place:
#include <memory>
class A: public enable_shared_from_this<A> {
};
A* ap1 =new A();
shared_ptr<A> ap2(ap1); // First prepare a shared pointer to the object and hold it!
// Then get a shared pointer to the object from the object itself
shared_ptr<A> ap3 = ap1->shared_from_this();
int c3 =ap3.use_count(); // =2: pointing to the same object
Remarque (2) vous ne pouvez pas appeler enable_shared_from_this
dans le constructeur.
#include <memory> // enable_shared_from_this
class Widget : public std::enable_shared_from_this< Widget >
{
public:
void DoSomething()
{
std::shared_ptr< Widget > self = shared_from_this();
someEvent -> Register( self );
}
private:
...
};
int main()
{
...
auto w = std::make_shared< Widget >();
w -> DoSomething();
...
}
Si vous utilisez shared_from_this()
sur un objet n'appartenant pas à shared_ptr
, tel qu'un objet automatique local ou un objet global, le comportement est indéfini. Depuis C ++ 17, il lance std::bad_alloc
place.
Utiliser shared_from_this()
partir d'un constructeur équivaut à l'utiliser sur un objet n'appartenant pas à shared_ptr
, car les objets sont possédés par shared_ptr
après le retour du constructeur.
Casting std :: shared_ptr pointeurs
Il est impossible d'utiliser directement static_cast
, const_cast
, dynamic_cast
et reinterpret_cast
sur std::shared_ptr
pour récupérer une propriété de partage de pointeur avec le pointeur étant passé comme argument. Au lieu de cela, les fonctions std::static_pointer_cast
, std::const_pointer_cast
, std::dynamic_pointer_cast
et std::reinterpret_pointer_cast
doivent être utilisées:
struct Base { virtual ~Base() noexcept {}; };
struct Derived: Base {};
auto derivedPtr(std::make_shared<Derived>());
auto basePtr(std::static_pointer_cast<Base>(derivedPtr));
auto constBasePtr(std::const_pointer_cast<Base const>(basePtr));
auto constDerivedPtr(std::dynamic_pointer_cast<Derived const>(constBasePtr));
Notez que std::reinterpret_pointer_cast
n'est pas disponible en C ++ 11 et C ++ 14, car il était uniquement proposé par N3920 et adopté dans Library Fundamentals TS en février 2014 . Cependant, il peut être implémenté comme suit:
template <typename To, typename From>
inline std::shared_ptr<To> reinterpret_pointer_cast(
std::shared_ptr<From> const & ptr) noexcept
{ return std::shared_ptr<To>(ptr, reinterpret_cast<To *>(ptr.get())); }
Ecrire un pointeur intelligent: value_ptr
Un value_ptr
est un pointeur intelligent qui se comporte comme une valeur. Une fois copié, il copie son contenu. Une fois créé, il crée son contenu.
// Like std::default_delete:
template<class T>
struct default_copier {
// a copier must handle a null T const* in and return null:
T* operator()(T const* tin)const {
if (!tin) return nullptr;
return new T(*tin);
}
void operator()(void* dest, T const* tin)const {
if (!tin) return;
return new(dest) T(*tin);
}
};
// tag class to handle empty case:
struct empty_ptr_t {};
constexpr empty_ptr_t empty_ptr{};
// the value pointer type itself:
template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>,
class Base=std::unique_ptr<T, Deleter>
>
struct value_ptr:Base, private Copier {
using copier_type=Copier;
// also typedefs from unique_ptr
using Base::Base;
value_ptr( T const& t ):
Base( std::make_unique<T>(t) ),
Copier()
{}
value_ptr( T && t ):
Base( std::make_unique<T>(std::move(t)) ),
Copier()
{}
// almost-never-empty:
value_ptr():
Base( std::make_unique<T>() ),
Copier()
{}
value_ptr( empty_ptr_t ) {}
value_ptr( Base b, Copier c={} ):
Base(std::move(b)),
Copier(std::move(c))
{}
Copier const& get_copier() const {
return *this;
}
value_ptr clone() const {
return {
Base(
get_copier()(this->get()),
this->get_deleter()
),
get_copier()
};
}
value_ptr(value_ptr&&)=default;
value_ptr& operator=(value_ptr&&)=default;
value_ptr(value_ptr const& o):value_ptr(o.clone()) {}
value_ptr& operator=(value_ptr const&o) {
if (o && *this) {
// if we are both non-null, assign contents:
**this = *o;
} else {
// otherwise, assign a clone (which could itself be null):
*this = o.clone();
}
return *this;
}
value_ptr& operator=( T const& t ) {
if (*this) {
**this = t;
} else {
*this = value_ptr(t);
}
return *this;
}
value_ptr& operator=( T && t ) {
if (*this) {
**this = std::move(t);
} else {
*this = value_ptr(std::move(t));
}
return *this;
}
T& get() { return **this; }
T const& get() const { return **this; }
T* get_pointer() {
if (!*this) return nullptr;
return std::addressof(get());
}
T const* get_pointer() const {
if (!*this) return nullptr;
return std::addressof(get());
}
// operator-> from unique_ptr
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&... args ) {
return {std::make_unique<T>(std::forward<Args>(args)...)};
}
Ce value_ptr particulier n'est vide que si vous le construisez avec empty_ptr_t
ou si vous en empty_ptr_t
. Il expose le fait que c'est un explicit operator bool() const
unique_ptr
, donc explicit operator bool() const
fonctionne dessus. .get()
a été modifié pour renvoyer une référence (car elle n'est presque jamais vide) et .get_pointer()
renvoie un pointeur à la place.
Ce pointeur intelligent peut être utile pour les cas pImpl
, où nous voulons une sémantique de valeur, mais nous ne voulons pas non plus exposer le contenu de pImpl
dehors du fichier d'implémentation.
Avec un Copier
par défaut, il peut même gérer des classes de base virtuelles qui savent comment produire des instances de leurs dérivés et les transformer en types de valeur.