L'une des fonctionnalité majeure introduit par le C++11 est les smart pointers. C'est une reprise de la librairie BOOST. L'utilisation de pointeurs est indispensable à une bonne conception (observer, injection de dépendance, factory, ...). Or, la bonne gestion des pointeurs devient un casse-tête à partir du moment que le pointeur est partagé.
Il remplace la précédente fonctionnalité std::auto_ptr qui souffrait des limites du C++98 (notion de déplacement inexistante, conteneurs non adaptés).
std::unique_ptr est souvent utilisé comme type de retour dans les fonctions fabrique d'objet.
In [1]:
.rawInput
Out[1]:
In [2]:
#include <iostream>
#include <string>
#include <map>
#include <memory>
#include <functional>
class Animal
{
public:
virtual ~Animal()
{
std::cout << "Animal dort" << std::endl;
}
virtual void crie() const = 0;
};
class Chien : public Animal
{
private:
std::string nom;
public:
Chien()
: nom("Le chien")
{}
Chien(const std::string& pNom)
: nom(pNom)
{}
virtual ~Chien() = default;
virtual void crie() const override
{
std::cout << nom << " aboie" << std::endl;
}
};
class Chat : public Animal
{
private:
std::string nom;
public:
Chat()
: nom("Le chat")
{}
Chat(const std::string& pNom)
: nom(pNom)
{}
virtual ~Chat() = default;
virtual void crie() const override
{
std::cout << nom << " miaule" << std::endl;
}
};
template <typename ...Ts>
class FabriqueAnimal
{
private:
using key_t = std::string;
using fabrique_t = std::function<std::unique_ptr<Animal>(Ts...)>;
std::map<key_t, fabrique_t> fabriqueMap;
template <typename T>
static auto creeFabrique()
{
return [](auto&& ...params){return std::make_unique<T>(std::forward<decltype(params)>(params)...);};
}
public:
FabriqueAnimal()
{
fabriqueMap["chien"] = creeFabrique<Chien>();
fabriqueMap["chat"] = creeFabrique<Chat>();
}
std::unique_ptr<Animal> cree(const key_t& key, Ts&&... params) const
{
std::unique_ptr<Animal> animal = nullptr;
const auto it = fabriqueMap.find(key);
if (it != fabriqueMap.end())
{
animal = it->second(std::forward<Ts>(params)...);
}
return animal;
}
};
Out[2]:
In [3]:
.rawInput
Out[3]:
In [4]:
{
const FabriqueAnimal<> animalerie;
const std::unique_ptr<Animal> dog = animalerie.cree("chien");
dog->crie();
}
std::cout << std::endl;
using FabriqueAnimalNomme = FabriqueAnimal<std::string>;
const FabriqueAnimalNomme refuge;
refuge.cree("chat", "nyan cat")->crie();
Out[4]:
In [5]:
using vector_ptr_t = std::vector<std::shared_ptr<Animal>>;
vector_ptr_t vaccine;
{
vector_ptr_t animaux;
animaux.push_back(refuge.cree("chat", "nyan cat"));
animaux.push_back(refuge.cree("chien", "duck hunt dog"));
// copie des pointeurs
for(auto&& animal : animaux)
{
vaccine.push_back(animal);
}
animaux.push_back(refuge.cree("chien", "lassie"));
}
for(auto&& animal : vaccine)
{
animal->crie();
}
Out[5]:
Remarque: Il faut toutefois faire attention lors de l'utilisation de pointeurs bruts (exemple: this). Cependant, la STL propose des mécanismes pour manier ces pointeurs en toute fiabilité (std::enable_shared_from_this).
Les pointeurs sont aussi très utilisés dans le pattern observer. Ce pattern a pour particularité de conserver un pointeur sur l'objet observer mais sans pour autant détenir la ressource. Cependant, les observables doivent s'assurer que le pointeur n'est pas détruit avant d'y accéder. std::weak_ptr répond parfaitement à cette double exigence.
Ce type de pointeur est aussi très utilisé pour la création de cache (Des pointeurs sont mémorisés afin d'éviter une répetition de construction-destruction d'un même objet).
In [6]:
class Notifier
{
public:
virtual ~Notifier() = default;
virtual void Notify() = 0;
};
class HelloNotification : public Notifier
{
public:
HelloNotification()
{}
virtual void Notify() override
{
std::cout << "Hello" << std::endl;
}
};
class BonjourNotification : public Notifier
{
public:
BonjourNotification()
{}
virtual void Notify() override
{
std::cout << "Bonjour" << std::endl;
}
};
class Subject
{
private:
using vector_ptr = std::vector<std::weak_ptr<Notifier>>;
vector_ptr notifiers;
void NotifyAll()
{
// Notifier tous les observers
for (auto&& n : notifiers)
{
std::shared_ptr<Notifier> ptr = n.lock();
if (ptr != nullptr)
{
ptr->Notify();
}
}
}
public:
Subject()
{}
void Add(const std::weak_ptr<Notifier>& n)
{
notifiers.push_back(n);
}
void Change()
{
NotifyAll();
}
// Méthode de désenregistrement
// ...
};
Subject s;
std::shared_ptr<Notifier> a = std::make_shared<HelloNotification>();
s.Add(a);
{
// Execution sur un autre thread
std::shared_ptr<Notifier> b = std::make_shared<BonjourNotification>();
s.Add(b);
}
s.Change();
Out[6]:
Pour deux raisons principalement :
std::make_shared<Objet>() que std::shared_ptr<Objet>(new Objet())c'est plus sûre vis à vis des exceptions. Par exemple, le code suivant produit une fuite mémoire si une exception se produit lors de l'évaluation de getCategory() :
log(std::shared_ptr<String>(new String("Erreur")), getCategory())
L'évaluation de new String("Erreur") est réalisée avant getCategory() et std::shared_ptr().
Les librairies C ou les bibliothèques graphiques disposent généralement de fonctions de création et de destruction d'objets. Les pointeurs intelligents permettent de définir l'appel à une fonction de destruction personnalisée.
Le conseil précédent indique d'utiliser une fonction de création à la place du constructeur de std::shared_ptr ou de std::unique_ptr. Heureusement, l'implémentation d'une méthode de création est assez simple.
In [7]:
.rawInput
Out[7]:
In [8]:
template <typename T>
void delete_with_log(T* ptr)
{
std::cout << "Destruction " << *ptr << std::endl;
delete ptr;
}
template <typename T>
using unique_log_ptr = std::unique_ptr<T,decltype(&delete_with_log<T>)>;
template <typename T, typename ...Ts>
unique_log_ptr<T> make_unique_with_log(Ts&&... params)
{
return unique_log_ptr<T>(new T(std::forward<Ts>(params)...), &delete_with_log<T>);
}
// pour un shared_ptr, la syntaxe est plus simple
template <typename T, typename ...Ts>
std::shared_ptr<T> make_shared_with_log(Ts&&... params)
{
return std::shared_ptr<T>(new T(std::forward<Ts>(params)...), &delete_with_log<T>);
}
Out[8]:
In [9]:
.rawInput
Out[9]:
In [10]:
make_unique_with_log<int>(42);
make_shared_with_log<std::string>(2, '0');
Out[10]:
In [ ]: