C++懒惰的单例挂在加载时

C++ lazy singleton hangs on loading

本文关键字:加载 单例挂 C++      更新时间:2023-10-16

我有懒惰的单例类,需要在第一次调用时使用boost进行序列化。

头文件:

class Singleton
{
public:
    static Singleton& Instance()
    {
        static Singleton theSingleInstance;
        return theSingleInstance;
    }
    void load();
private:
    Singleton();
    Singleton(const Singleton& root);
    Singleton& operator=(const Singleton&);
    std::map<std::string,std::string > m_desc;
    friend class boost::serialization::access;
    template<typename Archive>
    void serialize(Archive& arc, const unsigned int version)
    {
        arc & BOOST_SERIALIZATION_NVP(m_desc);
    }
    const char* FILENAME = "./config.xml";
};

源文件

#include "singleton.h"
Singleton::Singleton()
{
    load();
}

void Singleton::load()
{
    try
    {
        std::ifstream f(FILENAME);
        boost::archive::xml_iarchive arc(f);
        arc & boost::serialization::make_nvp("Config",Instance());
    }
    catch(...)
    {
        std::cout << "Exception" << std::endl;
    }
}

因此,当我尝试使用此单例启动代码时,它会挂起。使用调试器,我可以看到它不会多次转到load()方法(没关系)。当我暂停调试器时,它会在return theSingleInstance;行停止,但它也不会多次通过此行上的断点。我做错了什么?

从构造函数内部调用 load。这意味着theSingleInstance当您...呼叫Instance:

  #0   in Singleton::load at test.cpp <test.cpp>
  #1   in Singleton::Singleton at test.cpp <test.cpp>
  #2   in Singleton::Instance at test.cpp <test.cpp>
  #3   in main at test.cpp <test.cpp>

由于构造 c++11 函数本地静态保证是线程安全的,这意味着 - 在您的实现中 - 执行将阻塞,直到实例完全构造(或构造失败,因此可以重试)。

当然,这永远不会发生,因为建筑正在等待自己。

   0x00000000004030f5 <+629>:   callq  0x402be0 <__cxa_guard_acquire@plt>
=> 0x00000000004030fa <+634>:   test   %eax,%eax
   0x00000000004030fc <+636>:   je     0x402ff1 <Singleton::load()+369>
当然,正如

您已经发现的那样,通过在构造实例时不使用外部访问器可以解决此问题。

您可能需要考虑为单例提供值语义的做法 - 以防有一天您不希望它不再是单例并且希望避免重构。

另一个优点是它提供了完整的封装:

例如:

// config.h - extremely sparse interface
#include <string>
struct config
{
    /// Return a named value from the program's configuration
    /// @param name is the name of the required parameter
    /// @post the config file shall be cached
    /// @return the value if it exists
    /// @except std::invalid_argument if the value does not exist
    /// @except other exceptions if the config file does not load
    ///
    const std::string& value(const std::string& name) const;
private:
    struct impl;
    static impl& get();
};
// config.cpp - the implementation
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <fstream>
#include <map>
struct config::impl
{
    impl()
    {
        std::ifstream f(FILENAME);
        boost::archive::xml_iarchive arc(f);
        arc & boost::serialization::make_nvp("Config", *this);
    }
    std::map<std::string,std::string > m_desc;
    friend class boost::serialization::access;
    template<typename Archive>
    void serialize(Archive& arc, const unsigned int version)
    {
        arc & BOOST_SERIALIZATION_NVP(m_desc);
    }
    static constexpr const char* FILENAME = "./config.xml";
};
config::impl& config::get() {
    static impl _;
    return _;
}
const std::string& config::value(const std::string& name) const
{
    return get().m_desc.at(name);
}
// demo.cpp
// note - config is copyable, and this has almost no cost
void do_something_with(config cfg)
{
}
struct no_config {};   // some other config source?
// now I can switch on config type at compile time - code can be
// more generic
void do_something_with(no_config)
{
}
int main()
{
    // single-use
    std::string my_value = config().value("foo");
    // pass my config source to a function        
    do_something_with(config());
    // a similar function that uses a different config source
    do_something_with(no_config());
    return 0;
}

答案很简单:替换这一行

arc & boost::serialization::make_nvp("Config",Instance());

arc & boost::serialization::make_nvp("Config",*this);