重新创建一个 shared_ptr-based 单例
Re-creating a shared_ptr-based singleton
有一个 static shared_ptr<MyClass> get()
里面有一个 weak_ptr
使用 ptr.lock()
.
提供共享指针
当ptr.lock()
给出一个空指针时,应该重新创建单例。
但它是否保证(不)前一个单例的析构函数已经完成?对此可以做些什么?
对此可以做些什么?停止使用糟糕的编程风格。
如果您要使用单例,那么它应该是 单例:一个实例,句号。不需要用智能指针来管理它的生命周期;它总是在那里。销毁它只是为了以后重新创建它有什么意义?特别是如果没有为重新创建函数提供特殊参数以便稍后重新创建它?
但是,对于您的问题:
does it guarantee (it doesn't) that the destructor of the previous singleton has completed
重要吗?为了使对象的析构函数 started,对该对象的 shared_ptr
引用计数必须为零。所以 weak_ptr
已经是空的。对象的生命周期在其析构函数 开始 时结束(正如对象的生命周期在其构造函数完成时开始)。所以单例本身已经被破坏了;你只是在做清理工作。
因此在旧实例的析构函数的调用堆栈中创建新的单例实例没有问题。它根本不会访问自己。
线程
在多线程环境中,如果 get
函数中没有某种锁 returns/creates 单例,这种接口已经严重损坏。如果没有这种互斥,多个线程可能会尝试同时创建单例,这可能会导致构建多个单例实例。
至于单例本身内的资源,此类资源的释放必须由某种形式的互斥机制来管理。唯一一次资源 本身 是单例。但与我们正在谈论的单例不同的是,它不能被多段代码所拥有。
在这种情况下,您的单身人士根本不应该拥有该资源的所有权。它可以引用它,但不能销毁或创建它。
But does it guarantee (it doesn't) that the destructor of the previous singleton has completed? What can be done about that?
这是一个不寻常的请求,但我知道如果您要控制外部单例资源,它可能有必要。
这是我的解决方案。
确保在生产中使用之前彻底检查它
#include <memory>
#include <mutex>
#include <condition_variable>
struct tricky_object
{
};
class tricky_cache
{
struct statics {
std::mutex _m;
std::condition_variable _deleted;
bool _exists = false;
std::weak_ptr<tricky_object> _cache;
};
static statics& get() {
static statics _;
return _;
}
public:
static
std::shared_ptr<tricky_object> acquire()
{
// get static data
auto& data = get();
// lock the cache's mutex
auto lock = std::unique_lock<std::mutex>(data._m);
std::shared_ptr<tricky_object> candidate;
// wait on the condition variable for the following conditions to be true:
data._deleted.wait(lock, [&data, &candidate] {
// either the object is in play and we have acquired another reference...
candidate = data._cache.lock();
if (candidate)
return true;
// ... or (if not) the previous object is actually dead and buried.
return !data._exists;
});
// at this point we still own the lock and wait must have returned true, so...
// if we own the candidate then it was already in play
if (candidate)
return candidate;
// otherwise the previous object is certainly destroyed and we may create another
data._cache = candidate = std::shared_ptr<tricky_object>(new tricky_object(),
[&data](tricky_object*p) {
// but the custom deleter needs some trickery
delete p;
if (p) {
auto lock = std::unique_lock<std::mutex>(data._m);
data._exists = false;
lock.unlock();
data._deleted.notify_all();
}
});
// and we should record the fact that the object now exists...
data._exists = true;
lock.unlock();
// ... and inform all waiters that they may continue acquiring
data._deleted.notify_all();
return candidate;
}
};
int main()
{
auto p = tricky_cache::acquire();
return 0;
}
有一个 static shared_ptr<MyClass> get()
里面有一个 weak_ptr
使用 ptr.lock()
.
当ptr.lock()
给出一个空指针时,应该重新创建单例。
但它是否保证(不)前一个单例的析构函数已经完成?对此可以做些什么?
对此可以做些什么?停止使用糟糕的编程风格。
如果您要使用单例,那么它应该是 单例:一个实例,句号。不需要用智能指针来管理它的生命周期;它总是在那里。销毁它只是为了以后重新创建它有什么意义?特别是如果没有为重新创建函数提供特殊参数以便稍后重新创建它?
但是,对于您的问题:
does it guarantee (it doesn't) that the destructor of the previous singleton has completed
重要吗?为了使对象的析构函数 started,对该对象的 shared_ptr
引用计数必须为零。所以 weak_ptr
已经是空的。对象的生命周期在其析构函数 开始 时结束(正如对象的生命周期在其构造函数完成时开始)。所以单例本身已经被破坏了;你只是在做清理工作。
因此在旧实例的析构函数的调用堆栈中创建新的单例实例没有问题。它根本不会访问自己。
线程
在多线程环境中,如果 get
函数中没有某种锁 returns/creates 单例,这种接口已经严重损坏。如果没有这种互斥,多个线程可能会尝试同时创建单例,这可能会导致构建多个单例实例。
至于单例本身内的资源,此类资源的释放必须由某种形式的互斥机制来管理。唯一一次资源 本身 是单例。但与我们正在谈论的单例不同的是,它不能被多段代码所拥有。
在这种情况下,您的单身人士根本不应该拥有该资源的所有权。它可以引用它,但不能销毁或创建它。
But does it guarantee (it doesn't) that the destructor of the previous singleton has completed? What can be done about that?
这是一个不寻常的请求,但我知道如果您要控制外部单例资源,它可能有必要。
这是我的解决方案。
确保在生产中使用之前彻底检查它
#include <memory>
#include <mutex>
#include <condition_variable>
struct tricky_object
{
};
class tricky_cache
{
struct statics {
std::mutex _m;
std::condition_variable _deleted;
bool _exists = false;
std::weak_ptr<tricky_object> _cache;
};
static statics& get() {
static statics _;
return _;
}
public:
static
std::shared_ptr<tricky_object> acquire()
{
// get static data
auto& data = get();
// lock the cache's mutex
auto lock = std::unique_lock<std::mutex>(data._m);
std::shared_ptr<tricky_object> candidate;
// wait on the condition variable for the following conditions to be true:
data._deleted.wait(lock, [&data, &candidate] {
// either the object is in play and we have acquired another reference...
candidate = data._cache.lock();
if (candidate)
return true;
// ... or (if not) the previous object is actually dead and buried.
return !data._exists;
});
// at this point we still own the lock and wait must have returned true, so...
// if we own the candidate then it was already in play
if (candidate)
return candidate;
// otherwise the previous object is certainly destroyed and we may create another
data._cache = candidate = std::shared_ptr<tricky_object>(new tricky_object(),
[&data](tricky_object*p) {
// but the custom deleter needs some trickery
delete p;
if (p) {
auto lock = std::unique_lock<std::mutex>(data._m);
data._exists = false;
lock.unlock();
data._deleted.notify_all();
}
});
// and we should record the fact that the object now exists...
data._exists = true;
lock.unlock();
// ... and inform all waiters that they may continue acquiring
data._deleted.notify_all();
return candidate;
}
};
int main()
{
auto p = tricky_cache::acquire();
return 0;
}