在破解编码访谈一书的第259页,给出了C ++中模板化的单例(我不想发布所有代码,以防它的副本正确)。
问题是将单例实现为模板,并假设一个名为Lock的类,确保其线程安全且异常安全。
对于使用双重锁定技术且Lock对象具有acquire()/ release()对的单例,你可能会得到答案。
但是这个课没有析构函数。这是一个错误吗?如果它有一个析构函数,因为类实例成员是静态的,析构函数只会在程序终止时被调用,如果程序终止,任何分配的内存都会被释放回系统。或者是吗?是否存在不会发生这种情况的情况,因此没有析构函数的单身人士会导致泄密?
其次,问题是说使单例异常安全。使用未捕获的新对象创建单例对象,并且Lock对象是静态的,因此这实际上是异常安全吗?如果没有用于创建单例的内存,则new抛出异常,但由于Lock对象是静态的,因此无法调用其release()方法,因此它永远不会被调用?
答案 0 :(得分:1)
首先,我要提醒的是,辛格尔顿被广泛认为是一种反模式 - 这似乎是一个好主意,但事实证明这是一个错误。
其次,很难确定没有看到代码,但我的直接猜测是,如果你想让它异常安全,你可能最好将它们提供的锁定对象包装在一个释放锁定的RAII包装器中它的dtor:
class real_lock {
Lock lock;
public:
real_lock() { lock.acquire(); }
~real_lock() { lock.release(); }
};
有了这个,异常安全(至少是Lock部分)非常简单。 OTOH,双重检查锁定也是一种反模式。几乎任何你使用它的东西都至少会在某些机器/某些情况下出现问题。
答案 1 :(得分:1)
异常安全并不意味着捕获异常。这意味着使用RAII和自动析构函数来确保在出现异常时正确进行清理。
对于互斥锁,正确的方法是使用静态互斥对象和自动范围保护样式获取/释放RAII对象。由于RAII对象具有自动存储功能,因此会在发生异常时释放互斥锁。
编辑:这是RAII类的正确形式
class scoped_lock_guard
{
Lock& m_lock;
public:
scoped_locK_guard(Lock& lock) : m_lock(lock) { lock.acquire(); }
~scoped_lock_guard() { m_lock.release(); }
};
Lock
对象本身必须以某种方式共享。
答案 2 :(得分:-1)
在本书的开头部分,作者建议她将用Java编写“几乎所有”解决方案(第42页)。
Java GC将负责静态实例的清理。