通过 std::unique_ptr 的 LazyArray 模板,这是双重检查习语的正确实现吗?

LazyArray template via std::unique_ptr, is this a proper implementation of the double-check idiom?

此代码是否使用 C++11 原子安全地实现双重检查习惯用法? 我在 "The C++ Programing Language 4th ed." 中看到了一个使用 atomic<bool> 的例子,我尽我所能使事情保持等价,但我没有信心。另外,这个可以改进吗?

由于 once_flag 的存储开销,我想避免使用 call_once

此 "LazyArray" 是为了减少内存而编写的,目的是通过对客户端代码进行最少的更改来替换数组(预计只会访问元素的一小部分)。从多个线程访问数组是一个不争的事实,由于性能原因,广泛锁定会产生问题。

/**
 * Lazy creation of array elements.
 * They will only be created when they are referenced.
 *
 * This "array" does not support iteration because it can have holes
 *
 * Array bounds isn't checked to keep it fast.
 * Destruction of the array destroys all the T objects (via the unique_ptr d'tor)
 */

template<class T, size_t size>
class LazyArray
{
    typedef LazyArray<T, size> mytype;
public:
    // copying is not allowed (unlike regular arrays)
    LazyArray(const LazyArray&) = delete;
    LazyArray& operator=(const LazyArray&) = delete;

    LazyArray(){}

    T& operator[](size_t i)
    {
        return at(i);
    }

    const T& operator[](size_t i) const
    {
        return const_cast<mytype *>(this)->at(i);
    }
private:
    using guard = std::lock_guard<std::mutex>;

    // get T object at index i by reference
    T& at(size_t i) // only non-const variant is implemented, const version will use const_cast
    {
        auto &p = m_array[i];
        std::atomic<T*> ap(p.get());

        if(!ap) // object not created yet
        {
            guard g(mtx);
            ap = p.get();
            if(!ap)
                p.reset(new T);
        }
        return *p;
    }

    std::unique_ptr<T> m_array[size];
    std::mutex mtx;
};


理论上没有。

支票:

        std::atomic<T*> ap(p.get());
        if(!ap) // object not created yet

应该是acquire对应这个版本:

                p.reset(new T);

其实不是。

失败的原因:

  1. 对象构造的某些部分new T在看到p分配了新值[=49]后可能对其他线程可见=]
  2. 赋值 p.reset 可能被撕裂,因为它是非原子的

可能,在某些平台上。

  1. 如果存储可以在其他存储之后重新排序(在 x86 上不会发生)或者如果编译器决定自己交换这些存储(不太可能),就会发生这种情况
  2. 指针大小的变量写入不会被撕裂

使用 std::atomic 解决这两个问题:

   std::atomic<T*> m_array[size];

确定您必须手动删除 T*。 我建议基于 std::atomic<T*> 创建 atomic_unique_ptr 并在那里使用它