Reputation: 12431
I'm coding a class with Singleton pattern.
class GroupListDAO {
public:
static GroupListDAO* getInstance() {
static GroupListDAO groupListDAO;
return &groupListDAO;
}
init(server::mysqldb::MysqlHelperTempalte* pHelper) {
mysqlHT = pHelper;
}
bool getUserHeartNum(uint32_t owner, uint32_t& totalNum);
bool setUserHeartNum(uint32_t owner, uint32_t totalNum, uint32_t update_time);
private:
MysqlHelperTempalte *mysqlHT;
GroupListDAO() = default;
~GroupListDAO() = default;
};
As you see, this class is used to connect to Mysql. So the member data mysqlHT
must be initialized before calling any other member functions.
In a word, the class user must use this class as below:
GroupListDAO *p = GroupListDAO::getInstance();
p->init(XXX); // init must be called before calling any other member functions
p->getUserHeartNum(...);
p->setUserHeartNum(...);
So I'm thinking if there is a way to force the class user to call the function init
. Meaning that if the class user codes like this:
GroupListDAO *p = GroupListDAO::getInstance();
p->getUserHeartNum(...);
p->setUserHeartNum(...);
Some compile-time error can be generated.
Ofc, you might say that we can if (mysqlHT == nullptr) { throw exception; }
in other member functions, but that would be runtime error, instead of compile-time error.
REAL CASE
One large project is developed by 5 developers. They are all using some Singleton objects. But they must say: Hey, I've initialized that, you guys can use it in your codes. Or, we are sorry, we all forget to initialize it...
Upvotes: 2
Views: 139
Reputation: 123566
As suggested in a comment, you can use an argument with a default:
getInstance(server::mysqldb::MysqlHelperTempalte* pHelper = nullptr)
On the one hand it isnt nice to let the caller pass the parameter but using it only on the first call. On the other hand your design suggests that there is one place where you know it is the first call to getInstance
(you don't want to init
it twice, right?). Hence I propose this:
class GroupListDAO {
public:
static GroupListDAO* createInstance(server::mysqldb::MysqlHelperTempalte* pHelper) {
return getInstance(pHelper);
}
static GroupListDAO* getInstance() {
return getInstance(nullptr);
}
private:
static GroupListDAO* getInstance(server::mysqldb::MysqlHelperTempalte* pHelper = nullptr) {
static GroupListDAO groupListDAO(pHelper);
return &groupListDAO;
}
};
GroupListDAO
perhaps needs to be a wrapper of the actual object and it can throw in its constructor when called with a nullptr
. I am not aware of an easy way to create a compiler error.
PS: Actually I think you have conflicting requirements. On the one hand you want a singleton that hides initialization from the caller. The instance can be obtained via a getInstance
without needing to care about the initialization. On the other hand you do want to care about initialization because the first call to getInstance
is "special". Both, the singleton pattern, and using an init
method, are not perfect idioms in isolation. When used together they are worse. I am not saying that it is an absolute no-go, but it isnt too surprising that a compromise has to be made when both approaches should be kept.
Upvotes: 3
Reputation: 3460
If you really want a compile error, you would have to get rid of your singleton pattern:
class GroupListDao
{
public:
GroupListDao(...) { /* do your initialization here */ }
bool getUserHeartNum(uint32_t owner, uint32_t& totalNum);
bool setUserHeartNum(uint32_t owner, uint32_t totalNum, uint32_t update_time);
// ...
}
This way you ensure that any instance of GroupListDao
has been initialized. You then would have to pass this instance around in your code. As consequence, you no longer can be sure that there is only one instance of GroupListDao
in your application at compile time (if you really want you could track the number of created instances in your constructor and raise errors at runtime).
Pick your poison ;)
Upvotes: 0