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

发布于 2025-01-14 10:19:14 字数 704 浏览 5 评论 0原文

此线程中,有关单例实例的说明如下:

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

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

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

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

附录:重载 operator() 来调用单例的 createItem() 方法是个好主意,这样 Item 就可以了可以像这样创建:ItemFactory::factory("id")

In this thread, the following is said about singleton instances:

The static variable can be static to the GetInstance() function, or it can be static in the Singleton class. There's interesting tradeoffs there.

What are these trade-offs? I am aware that, if declared as a static function variable, the singleton won't be constructed until the function is first called. I've also read something about thread-safety, but am unaware of what exactly that entails, or how the two approaches differ in that regard.

Are there any other major differences between the two? Which approach is better?

In my concrete example, I have a factory class set up as a singleton, and I'm storing the instance as a static const field in the class. I don't have a getInstance() method, but rather expect the user to access the instance directly, like so: ItemFactory::factory. The default constructor is private, and the instance is allocated statically.

Addendum: how good of an idea is it to overload operator() to call the createItem() method for the singleton, such that Items can be created like so: ItemFactory::factory("id")?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

悸初 2025-01-21 10:19:14

这些权衡是什么?

这是最重要的考虑因素:

static 数据成员在程序启动时的静态初始化期间被初始化。如果任何 static 对象依赖于单例,那么将会有一个 静态初始化顺序惨败

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

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

线程安全不是静态数据成员的问题。

哪种方法更好?

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

What are these trade-offs?

This is the most important consideration:

The static data member is initialized during the static initialization at the start of the program. If any static object depends on the singleton, then there will be a static initialization order fiasco.

The function local static object is initialized when the function is first called. Since whoever depends on the singleton will call the function, the singleton will be appropriately initialized and is not susceptible to the fiasco. There is still a - very subtle - problem with the destruction. If a destructor of a static object depends on the singleton, but the constructor of that object does not, then you'll end up with undefined behaviour.

Also, being initialized on the first time the function is called, means that the function may be called after the static initialization is done and main has been called. And therefore, the program may have spawned multiple threads. There could be a race condition on the initialization of the static local, resulting in multiple instances being constructed. Luckily, since C++11, the standard guarantees that the initialization is thread safe and this tradeoff no longer exists in conforming compilers.

Thread safety is not an issue with the static data member.

Which approach is better?

That depends on what your requirements are and what version of the standard you support.

无力看清 2025-01-21 10:19:14

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

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

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

我不建议您将单例实现为在进入 main() 函数之前初始化的全局非指针变量。线程安全问题将随着隐式缓存一致性开销而消失,但您无法以任何精确或可移植的方式控制全局变量的初始化顺序。

无论如何,这种选择不会产生任何永久性的设计影响。由于此实例将驻留在类的 private 部分中,因此您可以随时更改它。

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

I vote for static function variable. The newer C++ standard require automatic thread safety for initialization of such variables. It's implemented in GNU C++ for about ten years already. Visual Studio 2015 also supports this. If you make a static pointer variable holding reference to your singleton object, you'll have to deal with thread issues manually.

In the other hand, if you make a static member pointer field like shown in in the snippet below, you will be able to change it from other static methods, maybe re-init this field with other instance upon handling request to change program configuration. However, the snippet below contains a bug just to remind you how difficult multithreading is.

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

I wouldn't advise you to implement your singleton as a global non-pointer variable initialized before entry to the main() function. Thread safety issues will go away along with implicit cache coherency overhead, but you're not able to control the initialization order of your global variables in any precise or portable way.

Anyway, this choice doesn't force any permanent design implications. Since this instance will reside in the private section of your class you may always change it.

I don't think overloading of operator() for a factory is a good idea. operator() have "execute" semantics while in factory it's gonna stand for "create".

以为你会在 2025-01-21 10:19:14

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_file 对象希望在不使用时刷新/关闭自身。

所以输出应该如下所示:

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

What is the best approach to a singleton in c++?

Hide the fact that it's a singleton and give it value semantics.

How?

All singleton-ness ought to be an implementation detail. In this way, consumers of your class need not refactor their programs if you need to change the way you implement your singleton (or indeed if you decide that it should not really be a singleton after all).

Why ?

Because now your program never has to worry itself with references, pointers, lifetimes and whatnot. It just uses an instance of the object as if it were a value. Safe in the knowledge that the singleton will take care of whatever lifetime/resource requirements it has.

What about a singleton that releases resources when not in use?

no problem.

Here's an example of the two approaches hidden behind the facade of an object with value semantics.

imagine this use case:

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

where a jobbie object is just a facade for a singleton, but the shared_file object wants to flush/close itself when not in use.

so the output should look like this:

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

We can achieve this using the idiom, which I'll call '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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文