Implémentation d'un singleton avec des std::shared_ptr
Bonjour à tous,
Je viens à vous car je me heurte à un problème que je n'arrive pas à comprendre.
Je tente d'implémenter une classe singleton Template réutilisable ; en m'inspirant de ce que je trouve sur le net, j'arrive à une première implémentation (non encore thread-safe, mais c'est pas la question), avec des pointeurs classiques, qui marche :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
#ifndef DEFINE_SINGLETON
#define DEFINE_SINGLETON
#include <iostream>
// Classe de design pattern Singleton
template <typename T>
class Singleton {
protected :
// Pointeur vers l'instance unique
static T* singletonPtr;
// Constructeur et destructeur privés pour éviter l'appel depuis l'extérieur.
Singleton () {}
~Singleton() {}
public :
// Méthode statique pour accéder à l'instance unique du Singleton ; celle-ci est
// construite si elle n'était pas encore allouée.
static T* getInstance() {
if ( singletonPtr == nullptr ) singletonPtr = new T;
return singletonPtr;
}
// Méthode statique pour détruire l'instance unique de T
static void kill() {
if ( singletonPtr != nullptr) delete singletonPtr;
singletonPtr = nullptr;
}
};
// définition du singleton statique, intialisé sur nullptr
template <typename T>
T* Singleton<T>::singletonPtr = nullptr;
// Classe test utilisant la classe ci-dessus
class Test : public Singleton<Test> {
friend class Singleton<Test>;
private :
// Constructeur et destructeur private pour éviter l'instanciation/destruction
Test() {}
~Test(){}
// Constructeur de copie et opérateur d'affectation interdits
Test(const Test& other) = delete;
Test operator=(const Test& other) = delete;
public :
// une méthode quelconque
void doSomething() { std::cout << "doSomething" << std::endl; }
};
#endif // ndef DEFINE_SINGLETON |
et le main pour tester :
Code:
1 2 3 4 5 6 7 8 9
|
#include <iostream>
#include "SingletonSP.hpp"
int main() {
Test::getInstance()->doSomething();
Test::kill();
return 0;
}; |
Ceci compile et marche parfaitement.
Ensuite, j'essaie une version avec des shared_ptr :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
#ifndef DEFINE_SINGLETON
#define DEFINE_SINGLETON
#include <iostream>
#include <memory>
// Classe de design pattern Singleton
template <typename T>
class SingletonSP {
protected :
// Pointeur vers l'instance unique
static std::shared_ptr<T> singletonPtr;
// Constructeur et destructeur privés pour éviter l'appel depuis l'extérieur.
SingletonSP () {}
~SingletonSP() {}
public :
// Méthode statique pour accéder à l'instance unique du Singleton ; celle-ci est
// construite si elle n'était pas encore allouée.
static std::shared_ptr<T> getInstance() {
if ( !singletonPtr ) singletonPtr = std::make_shared<T>();
return singletonPtr;
}
// kill() n'est a priori plus nécessaire, puisque singletonPtr sera détruit à la fin du scope
// global, désalouant automatiquement l'objet pointé.
};
// la définition du singleton statique, non initailisée car encapsulée.
template <typename T>
std::shared_ptr<T> Singleton<T>::singletonPtr;
// Classe test utilisant la classe ci-dessus
class Test : public SingletonSP<Test> {
friend class Singleton<Test>;
friend std::shared_ptr<Test> std::make_shared<Test>(); // friend pour avoir accès au constructeur
friend class std::shared_ptr<Test>; // friend pour avoir accès au destructeur
private :
// Constructeur et destructeur private pour éviter l'instanciation/destruction
Test() {}
~Test(){}
// Constructeur de copie et opérateur d'affectation interdits
Test(const Test& other) = delete;
Test operator=(const Test& other) = delete;
public :
// une méthode quelconque
void doSomething() { std::cout << "doSomething" << std::endl; }
};
#endif // ndef DEFINE_SINGLETON |
et le main à peine modifié :
Code:
1 2 3 4 5 6 7 8
|
#include <iostream>
#include "SingletonSP.hpp"
int main() {
Test::getInstance()->doSomething(); // <-ligne 9
return 0;
}; |
Et là, la belle erreur de compilation, qui en clair me dit qu'à la ligne 9 je fais appel aux constructeur et destructeur de Test, ce qui est interdit car ils sont private.
L'erreur exacte du compilo est, sans toutes les inclusions de la stl (que j'ai remplacées par (...) pour rendre le truc lisible) :
(...)
SingletonSP.hpp:28:62: required from ‘static std::shared_ptr<_Tp1> SingletonSP<T>::getInstance() [with T = Test]’
main.cpp:6:9: required from here
SingletonSP.hpp:52:3: error: ‘Test::Test()’ is private
Test() {}
(...)
main.cpp:9:2: required from here
SingletonSP.hpp:53:3: error: ‘Test::~Test()’ is private
~Test(){}
(...)
Voilà, j'imagine qu'il s'agit d'une subtilité dans l'utilisation des shared_ptr que je ne vois pas, mais justement je ne la vois pas...
Précision, j'utilise g++ 4.8.1 avec les options -Wall, -Wextra et -std=c++11
Merci d'avance,
whityranger
Est-ce vraiment une bonne idée
Bonsoir
je ne pense pas que le concept de singleton et de shared_ptr puisse faire bon ménage.
Comme pour la plupart des conteneurs de la stl (probablement presque tous) l'un des pré-requis pour leur utilisation est de leur fournir des objets qui puissent-être construit par copie, Or c'est justement ce que l'on souhaite éviter avec le singleton.
si on lit attentivement la doc d'origine
http://www.boost.org/doc/libs/1_55_0...lt_constructor
Citation:
Every shared_ptr meets the CopyConstructible, MoveConstructible, CopyAssignable and MoveAssignable requirements of the C++ Standard Library, and can be used in standard library containers. Comparison operators are supplied so that shared_ptr works with the standard library's associative containers.
d'ailleur le compilateur te dit clairement que pour ton exemple de code shared_ptr ne peut pas accéder ni au constructeur Test::Test(), ni au destructeur Test::~Test() car ces dernier sont privés....