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....