web-dev-qa-db-fra.com

Est-il possible de s'assurer qu'une fonction n'est appelée que pendant l'étape d'initialisation statique

Je me demandais s'il était possible de s'assurer qu'une fonction n'est appelée que pendant l'étape d'initialisation statique d'un programme?

Par exemple, disons que j'ai une classe singleton qui contient un objet std::map et expose ses méthodes insert et at. Je voudrais m'assurer que la lecture des données à partir de celle-ci (la méthode at) est thread-safe, ce qui, à ma connaissance, nécessite de s'assurer que rien ne modifie les données (c'est-à-dire en utilisant la méthode insert.).

La carte est destinée à être remplie uniquement lors de l'initialisation statique, heure à laquelle je suppose qu'il n'y a qu'un seul thread. Existe-t-il un moyen de ne pas éviter les appels d'utilisateurs non guidés insert une fois que main() a commencé?


Exemple de code

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

Inutile (?) De dire que le code actuel est bien plus complexe que ce simple exemple.


Edition après solution: Il semble que le meilleur moyen de rendre ceci aussi sûr que possible est de conserver une variable status qui enregistre si le singleton est en mode 'insert' ou 'read' et agit en conséquence. Merci à tous pour leurs idées et suggestions!

7
extiam

Je suppose que vous souhaitez également utiliser la méthode 'at' pour configurer votre application . Pourquoi ne pas ajouter une méthode 'lock' et appeler cette simple fonction en tant que première fonction principale? 

#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;
}
4
Jürgen

Si vous pouvez garantir que l'utilisateur ne lira pas la carte avant la main() à l'étape d'initialisation, une solution consiste à construire une carte statique uniquement pour l'initialisation, puis à la déplacer vers le singleton lors de la construction du singleton.

Étant donné que la construction a lieu au premier appel de instance(), vous pouvez être sûr que la carte est correctement initialisée.

Un autre appel à insert n'aura alors aucun effet sur le singleton. Vous pouvez également ajouter un mutex pour protéger insert afin d’éviter que UB ne soit en situation de concurrence.

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
llllllllll

Tout comme Jürgen avec la méthode non Java mais la méthode c/c ++ de créer un singleton (un espace de noms).

Pourquoi faire comme ça:

  • il est beaucoup plus efficace et plus facile à optimiser au moment de la liaison, car il n'est pas nécessaire de déréférencer this pour accéder à l'état;
  • il n'est pas nécessaire de maintenir le code pour garantir l'unicité: l'éditeur de liens assure l'unicité.

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

De plus, si le singleton doit être utilisé dans un code générique, une structure peut être utilisée pour cela:

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

Demain, arrêtez de coder en Java :).

1
Oliv