C++ getInstance()方法中作为静态字段与静态变量的Singleton实例

C++ getInstance()方法中作为静态字段与静态变量的Singleton实例,c++,design-patterns,singleton,c++14,static-members,C++,Design Patterns,Singleton,C++14,Static Members,在中,以下是关于单例实例的说明: 静态变量可以是GetInstance()函数的静态变量,也可以是Singleton类中的静态变量。这里有一些有趣的权衡 这些权衡是什么?我知道,如果声明为静态函数变量,则在首次调用函数之前不会构造单例。我也读过一些关于线程安全的文章,但我不知道这到底意味着什么,也不知道这两种方法在这方面有什么不同 这两者之间还有其他主要区别吗?哪种方法更好 在我的具体示例中,我将工厂类设置为单例,并将实例存储为类中的static const字段。我没有getInstance()

在中,以下是关于单例实例的说明:

静态变量可以是GetInstance()函数的静态变量,也可以是Singleton类中的静态变量。这里有一些有趣的权衡

这些权衡是什么?我知道,如果声明为
静态
函数变量,则在首次调用函数之前不会构造单例。我也读过一些关于线程安全的文章,但我不知道这到底意味着什么,也不知道这两种方法在这方面有什么不同

这两者之间还有其他主要区别吗?哪种方法更好

在我的具体示例中,我将工厂类设置为单例,并将实例存储为类中的
static const
字段。我没有
getInstance()
方法,而是希望用户直接访问实例,就像这样:
ItemFactory::factory
。默认构造函数是私有的,实例是静态分配的


附录:重载
operator()
调用单例的
createItem()
方法,这样就可以像这样创建
Item
s有多好:
ItemFactory::factory(“id”)

我投票支持静态函数变量。新的C++标准要求自动线程安全来初始化这些变量。它已经在GNU C++中实现了大约十年。Visual Studio 2015也支持这一点。如果创建一个静态指针变量来保存对singleton对象的引用,则必须手动处理线程问题

另一方面,如果您创建一个静态成员指针字段,如下面的代码段中所示,您将能够从其他静态方法中更改它,或者在处理更改程序配置的请求时使用其他实例重新初始化此字段。然而,下面的代码片段包含一个bug,只是为了提醒您多线程有多困难

class ItemFactory {
static std::atomic_flag initialized = ATOMIC_FLAG_INIT;
static std::unique_ptr<ItemFactory> theFactoryInstance;
public:
static ItemFactory& getInstance() {
  if (!initialized.test_and_set(std::memory_order_acquire)) {
    theFactoryInstance = std::make_unique<ItemFactory>();
  }
  return *theFactoryInstance;
}
};
类项目工厂{
静态std::atomic_flag initialized=atomic_flag_INIT;
静态标准::工厂状态的唯一性;
公众:
静态ItemFactory&getInstance(){
如果(!initialized.test_和_set(std::memory_order_acquire)){
FactoryInstance=std::make_unique();
}
返回*工厂实例;
}
};
我不建议您在进入
main()
函数之前将单例实现为一个全局非指针变量。线程安全问题将随着隐式缓存一致性开销而消失,但您无法以任何精确或可移植的方式控制全局变量的初始化顺序

无论如何,这一选择并不强制任何永久性的设计含义。由于此实例将位于类的
private
部分,因此您可以随时更改它

我不认为为工厂重载操作符()是个好主意
operator()
具有“执行”语义,而在工厂中它将代表“创建”

这些权衡是什么

这是最重要的考虑:

静态
数据成员在程序开始时的静态初始化期间初始化。如果任何
静态
对象依赖于单例,则将有一个

函数本地
静态
对象在首次调用函数时初始化。由于依赖单例的人将调用该函数,因此单例将被适当地初始化,并且不会受到失败的影响。破坏仍然存在一个非常微妙的问题。如果静态对象的析构函数依赖于单例,但该对象的构造函数不依赖于单例,那么最终将出现未定义的行为

此外,在第一次调用函数时进行初始化意味着可以在静态初始化完成并且调用了
main
之后调用该函数。因此,程序可能产生了多个线程。在初始化
static
local时可能存在争用条件,导致构造多个实例。幸运的是,自C++11以来,该标准保证了初始化是线程安全的,并且这种折衷在符合标准的编译器中不再存在

静态
数据成员不存在线程安全问题

哪种方法更好

这取决于您的需求以及您支持的标准版本

< C++中的单例最佳方法是什么?

隐藏它是单例的事实,并给它赋值语义

怎么做

所有的单例特性都应该是一个实现细节。通过这种方式,如果您需要更改实现单例的方式(或者如果您决定它毕竟不应该是单例的话),那么类的使用者就不需要重构他们的程序

为什么?

因为现在你的程序再也不用担心引用、指针、生命周期等等了。它只是使用对象的一个实例,就好像它是一个值一样。知道单身汉会照顾到自己的任何生命周期/资源需求是安全的

当不使用时释放资源的单例呢

没问题

下面是隐藏在具有值语义的对象外观后面的两种方法的示例

想象一下这个用例:

auto j1 = jobbie();
auto j2 = jobbie();
auto j3 = jobbie();

j1.log("doh");
j2.log("ray");
j3.log("me");

{
    shared_file f;
    f.log("hello");
}

{
    shared_file().log("goodbye");
}

shared_file().log("here's another");

shared_file f2;
{
    shared_file().log("no need to reopen");
    shared_file().log("or here");
    shared_file().log("or even here");
}
f2.log("all done");
其中,
jobbie
对象只是单个对象的外观,但是
shared_文件
对象希望在不使用时刷新/关闭自身

因此,输出应如下所示:

doh
ray
me
opening file
logging to file: hello
closing file
opening file
logging to file: goodbye
closing file
opening file
logging to file: here's another
closing file
opening file
logging to file: no need to reopen
logging to file: or here
logging to file: or even here
logging to file: all done
closing file
我们可以使用我称之为“value-semantics-is-a-facade-for-singleton”的成语来实现这一点:

#include <iostream>
#include <vector>

// interface
struct jobbie
{
    void log(const std::string& s);

private:
    // if we decide to make jobbie less singleton-like in future
    // then as far as the interface is concerned the only change is here
    // and since these items are private, it won't matter to consumers of the class
    struct impl;
    static impl& get();
};

// implementation

struct jobbie::impl
{
    void log(const std::string& s) {
        std::cout << s << std::endl;
    }
};

auto jobbie::get() -> impl& {
    //
    // NOTE
    // now you can change the singleton storage strategy simply by changing this code
    // alternative 1:
    static impl _;
    return _;

    // for example, we could use a weak_ptr which we lock and store the shared_ptr in the outer
    // jobbie class. This would give us a shared singleton which releases resources when not in use

}

// implement non-singleton interface

void jobbie::log(const std::string& s)
{
    get().log(s);
}

struct shared_file
{
    shared_file();

    void log(const std::string& s);

private:
    struct impl;
    static std::shared_ptr<impl> get();
    std::shared_ptr<impl> _impl;
};

// private implementation

struct shared_file::impl {

    // in a multithreaded program
    // we require a condition variable to ensure that the shared resource is closed
    // when we try to re-open it (race condition)
    struct statics {
        std::mutex m;
        std::condition_variable cv;
        bool still_open = false;
        std::weak_ptr<impl> cache;
    };

    static statics& get_statics() {
        static statics _;
        return _;
    }

    impl() {
        std::cout << "opening file\n";
    }
    ~impl() {
        std::cout << "closing file\n";
        // close file here
        // and now that it's closed, we can signal the singleton state that it can be
        // reopened

        auto& stats = get_statics();

        // we *must* use a lock otherwise the compiler may re-order memory access
        // across the memory fence
        auto lock = std::unique_lock<std::mutex>(stats.m);
        stats.still_open = false;
        lock.unlock();
        stats.cv.notify_one();
    }
    void log(const std::string& s) {
        std::cout << "logging to file: " << s << std::endl;
    }
};

auto shared_file::get() -> std::shared_ptr<impl>
{
    auto& statics = impl::get_statics();

    auto lock = std::unique_lock<std::mutex>(statics.m);
    std::shared_ptr<impl> candidate;
    statics.cv.wait(lock, [&statics, &candidate] {
        return bool(candidate = statics.cache.lock())
        or not statics.still_open;
    });
    if (candidate)
        return candidate;

    statics.cache = candidate = std::make_shared<impl>();
    statics.still_open = true;
    return candidate;
}


// interface implementation

shared_file::shared_file() : _impl(get()) {}
void shared_file::log(const std::string& s) { _impl->log(s); }

// test our class
auto main() -> int
{
    using namespace std;

    auto j1 = jobbie();
    auto j2 = jobbie();
    auto j3 = jobbie();

    j1.log("doh");
    j2.log("ray");
    j3.log("me");

    {
        shared_file f;
        f.log("hello");
    }

    {
        shared_file().log("goodbye");
    }

    shared_file().log("here's another");

    shared_file f2;
    {
        shared_file().log("no need to reopen");
        shared_file().log("or here");
        shared_file().log("or even here");
    }
    f2.log("all done");


    return 0;
}
#包括
#包括
//接口
结构工单
{
无效日志(const std::string&s);
私人:
//如果我们决定让未来的单身汉失业
//然后作为f