是否可以确保仅在 'static initialization' 步骤期间调用函数

Is it possible to ensure that a function is only called during the 'static initialization' step

我想知道是否可以确保仅在程序的静态初始化步骤中调用函数?

举个例子,假设我有一些单例 class,它包含一个 std::map 对象并公开了它的 insertat 方法。我想确保从中读取数据(at 方法)是线程安全的,据我所知,这需要确保没有任何内容正在修改数据(即使用 insert 方法)。

该映射旨在 在静态初始化期间填充,此时我假设只有一个线程。有什么方法可以确保在 main() 开始后不会有误导的用户调用 insert


示例代码

#include <map>
#include <string>

class Singleton {
  private:
    std::map<std::string, std::string> m_map;
  public:
    static Singleton& instance() {
      static Singleton theSingleton;
      return theSingleton;
    }
    static bool insert(const std::string& key, const std::string& value) {
      return instance().m_map.insert(std::make_pair(key, value) ).second;
    }
    static std::string at(const std::string& key) {
      return instance().m_map.at(key);
    }
};

static bool inserted = Singleton::insert("Hello", "World"); // fine

bool addItem(const std::string& key, const std::string& value) {
  return Singleton::insert(key, value); // not OK
}

不用说(?)实际代码比这个简单的例子要复杂得多。


解决方案后编辑:看起来尽可能安全的最好方法是维护一个status变量来记录单例是否在'insert' 或 'read' 模式并相应地执行操作。感谢大家的想法和建议!

如果在初始化阶段可以保证用户不会在main()之前读取map,一种解决方案是构造一个静态map,仅用于初始化,然后在单例运行时将其移动到单例中。正在建设中。

由于构造是在第一次调用 instance() 时发生的,因此您可以确定地图已正确初始化。

然后对 insert 的其他调用不会对单例产生影响。您还可以添加互斥锁来保护 insert 以避免 UB 出现竞争条件。

class Singleton {
  private:
    std::map<std::string, std::string> m_map;
    static auto& init_map() {
        static std::map<std::string, std::string> m;
        return m;
    }
    Singleton() {
        m_map = std::move(init_map());
        init_map().clear();
    }
  public:
    static Singleton& instance() {
      static Singleton theSingleton;
      return theSingleton;
    }
    static bool insert(const std::string& key, const std::string& value) {
      // you can also add mutex to protect here,
      // because calling insert from different threads without
      // protection will screw up its internal state, even if
      // the init_map becomes useless after main
      return init_map().insert(std::make_pair(key, value) ).second;
    }
    static std::string at(const std::string& key) {
      return instance().m_map.at(key);
    }
};

我猜您还想使用 'at' 方法来设置您的应用程序。 为什么不添加一个 'lock' 方法并在 main 中调用那个简单的第一个函数?

#include <map>
#include <string>

class Singleton {
private:
    std::map<std::string, std::string> m_map;
    bool m_locked;
    Singleton() : m_locked(false) { }

public:
    static Singleton& instance() {
        static Singleton theSingleton;
        return theSingleton;
    }

    static void lock() {
        instance().m_locked = true;
    }

    static bool insert(const std::string& key, const std::string& value) {
        if (instance().m_locked) { return false; }
        return instance().m_map.insert(std::make_pair(key, value)).second;
    }
    static std::string at(const std::string& key) {
        return instance().m_map.at(key);
    }
};

static bool inserted = Singleton::insert("Hello", "World"); // fine

bool addItem(const std::string& key, const std::string& value) {
    return Singleton::insert(key, value); // not OK
}

int main(int argc, char** argv)
{
    Singleton::lock();
    Singleton::insert("Hello2", "World2"); // fails
    return 0;
}

就像 Jürgen 使用非 Java 方式但 c/c++ 创建单例(即名称空间)的方式。

为什么要这样:

  • 在 link 时更高效且更容易优化,因为无需取消引用 this 即可访问状态;
  • 无需维护代码来确保唯一性:唯一性由 linker.
  • 确保

singleton.hpp

namespace singleton{
  void lock();
  bool instert(const std::string& key, const std::string& value);
  std::string at(const std::string& key)
  }

singleton.cpp

namespace singleton{
  namespace{
    inline decltype(auto) 
    get_map(){
      static std::map<std::string, std::string> m_map;
      return m_map;
      }
    bool m_locked=false; //OK guarenteed to happen before any dynamic initialization [basic.start.static]
    }

  void lock() {
    m_locked = true;
    }

  bool insert(const std::string& key, const std::string& value) {
    if (m_locked) { return false; }
    return get_map().insert(std::make_pair(key, value)).second;
    }

  std::string at(const std::string& key) {
    return get_map().at(key);
    }
  }

此外,如果必须在通用代码中使用单例,则可以使用结构:

struct singleton_type{
  static void lock() {singleton::lock();}
  static auto insert(const std::string& key, const std::string& value) {
      return singleton::insert(key,value);
      }
  static auto at(const std::string& key) {
      return singleton::at(key,value);
      }
  };
auto x = singleton_type{};
auto y = singleton_type{}; 
// x and y refers to the same and unique object file!!!

明天,停止编码 java :)。