如果未调用某些成员函数,是否可能生成编译时错误

Is it possible to generate a compile-time error if some member function hasn't been called

我正在用单例模式编写 class。

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;
};

如您所见,此 class 用于连接 Mysql。所以成员数据mysqlHT必须在调用任何其他成员函数之前初始化。

总之,class用户必须使用这个class如下:

GroupListDAO *p = GroupListDAO::getInstance();
p->init(XXX);    // init must be called before calling any other member functions
p->getUserHeartNum(...);
p->setUserHeartNum(...);

所以我在想是否有办法强制 class 用户调用函数 init。这意味着如果 class 用户代码是这样的:

GroupListDAO *p = GroupListDAO::getInstance();
p->getUserHeartNum(...);
p->setUserHeartNum(...);

可能会产生一些编译时错误。

Ofc,你可能会说我们可以在其他成员函数中if (mysqlHT == nullptr) { throw exception; },但那将是运行时错误,而不是编译时错误。

真实案例

一个大项目由5个开发人员开发。他们都在使用一些单例对象。但他们必须说:嘿,我已经初始化了,你们可以在代码中使用它。或者,不好意思,我们都忘记初始化了...

如评论中所建议,您可以使用默认参数:

getInstance(server::mysqldb::MysqlHelperTempalte* pHelper = nullptr)

一方面,让调用者传递参数但仅在第一次调用时使用它并不好。另一方面,您的设计表明有一个地方您知道它是对 getInstance 的第一次调用(您不想 init 它两次,对吧?)。因此我提议:

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 可能需要是实际对象的包装器,并且在使用 nullptr 调用时可以抛出其构造函数。我不知道创建编译器错误的简单方法。


PS:其实我觉得你的需求是矛盾的。一方面,您需要一个对调用者隐藏初始化的单例。可以通过 getInstance 获取实例,无需关心初始化。另一方面,您确实想关心初始化,因为对 getInstance 的第一次调用是“特殊的”。单例模式和使用 init 方法都不是孤立的完美习语。当一起使用时,它们会更糟。我并不是说这是绝对不行的,但是在应该保留两种方法的情况下必须做出妥协也就不足为奇了。

如果你真的想要一个编译错误,你将不得不摆脱你的单例模式:

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); 
  // ...
}

这样可以确保 GroupListDao 的任何实例都已初始化。然后您将不得不在您的代码中传递这个实例。因此,您无法再确定在编译时您的应用程序中只有一个 GroupListDao 实例(如果您真的想要,您可以在构造函数中跟踪创建实例的数量并在运行时引发错误)。 选择你的毒药 ;)