5

我目前正在学习如何使用 C++11 智能指针,同时使用 SDL 将 2D 游戏引擎作为一种爱好进行编程。但是,我在为 SDL 实现 OOp 包装器时遇到了问题。

目的是创建一个单例类,它在构造时初始化 SDL,并在销毁时关闭 SDL。单例类有一个静态方法getInstance返回一个shared_ptr单例,如果没有实例存在则构造单例,想法是单例的所有客户端都拥有shared_ptr它,当所有客户端都被销毁时,单例也被销毁. 我确实理解单例(和其他全局变量)通常很糟糕,但我认为这可能是适合单例的少数情况之一,因为只能使用一个 SDL 库。

问题在于shared_ptrgetInstance方法返回。实例不是使用相同的shared_ptr管理器对象,shared_ptr而是不相关的,并且销毁其中的一个会释放单例。

#include <iostream>
#include <memory>
using namespace std;

class Foo
{
public:
    ~Foo(){cout << "Foo <" << this << "> destroyed\n"; instance_ = nullptr;}
    static shared_ptr<Foo> getInstance()
    {
        if(instance_ == nullptr)
            instance_ = new Foo;
        //problem: the shared pointers created are unaware of each other
        return shared_ptr<Foo>(instance_);
    }
private:
    Foo(){cout << "Foo <" << this << "> constructed\n";}
    Foo(Foo& other){}
    void operator=(Foo& other){}
    static Foo* instance_;
};

Foo* Foo::instance_ = nullptr;

int main()
{
    shared_ptr<Foo> a = Foo::getInstance();
    shared_ptr<Foo> b = Foo::getInstance();
    shared_ptr<Foo> c = Foo::getInstance();
}

输出:

Foo <0x3e2a10> constructed
Foo <0x3e2a10> destroyed
Foo <0x3e2a10> destroyed
Foo <0x3e2a10> destroyed
4

2 回答 2

12

您的方法可能如下所示:

static shared_ptr<Foo> getInstance()
{
    static std::shared_ptr<Foo> instance = std::make_shared<Foo>();
    return instance;
}

这样只会创建一个静态实例(因为 C++11 这也是线程安全的),并且您总是从 static 返回一个副本shared_ptr,这意味着返回的所有共享指针共享所有权。

您最初的尝试从同一个普通指针创建了单独的实例shared_ptr,但这会导致所有权分离,因为不同的共享指针彼此不知道,并且每个指针都有自己的内部“共享计数”。


更新:重新阅读您的问题我认为您不想将生命周期延长到程序结束。考虑使用此方法在所有返回的共享指针超出范围后立即销毁实例:

static std::shared_ptr<Foo> getInstance()
{
    static std::weak_ptr<Foo> instance;
    static std::mutex mutex; // (a)
    const std::lock_guard< std::mutex > lock( mutex ); // (b)
    if( const auto result = instance.lock() ) return result;
    return ( instance = std::make_shared<Foo>() ).lock();
}

如果您不在多线程环境中,您可以删除标记为 (a) 和 (b) 的行。

请注意,这实际上是一个所谓的 phoenix-singleton,这意味着如果您收到的所有共享指针getInstance()都超出范围并且实例被删除,那么下一次调用getInstance()将创建另一个新的Foo.

于 2013-10-02T22:10:24.320 回答
2

与其在静态成员中保留原始指针,不如保留weak_ptr. 使用该lock()函数转换回shared_ptr; 如果它返回为空,那么您需要分配一个新的单例对象。

    static shared_ptr<Foo> getInstance()
    {
        shared_ptr<Foo> p = instance_.lock();
        if (!p)
        {
            p = new Foo;
            instance_ = p;
        }
        return p;
    }
private:
    static weak_ptr<Foo> instance_;
于 2013-10-02T23:16:45.907 回答