是否可以确保仅在“静态初始化”步骤中调用函数

时间:2018-12-19 09:58:54

标签: c++ c++11 static-initialization

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

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

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


示例代码

#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变量,该变量记录单例是“插入”还是“读取”模式并采取相应行动。感谢所有人的想法和建议!

3 个答案:

答案 0 :(得分:4)

我想您也想在设置应用程序时使用“ at”方法。 为什么不添加一个'lock'方法并在主函数中调用第一个函数那样简单呢?

memmap

答案 1 :(得分:2)

如果可以保证在初始化阶段用户不会在main()之前读取地图,则一种解决方案是构造一个仅用于初始化的静态地图,然后在构造单例时将其移至单例。

由于构造是在第一次调用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);
    }
};

答案 2 :(得分:1)

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

为什么要这样做

  • 由于不需要取消引用this来访问状态,因此在链接时效率更高并且更容易优化;
  • 无需维护代码来确保唯一性:链接器确保唯一性。

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进行编码:)。