Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ LazyArray模板通过std::unique_ptr,这是双重检查习惯用法的正确实现吗?_C++_Multithreading_Locking_Stdatomic_Double Checked Locking - Fatal编程技术网

C++ LazyArray模板通过std::unique_ptr,这是双重检查习惯用法的正确实现吗?

C++ LazyArray模板通过std::unique_ptr,这是双重检查习惯用法的正确实现吗?,c++,multithreading,locking,stdatomic,double-checked-locking,C++,Multithreading,Locking,Stdatomic,Double Checked Locking,这段代码是否使用C++11原子安全地实现了双重检查习惯用法? 我在《C++编程语言第四版》中看到了一个例子,使用了原子< /代码>,我尽我所能保持事物的等价性,但我没有信心。还有,这方面可以改进吗 我希望避免call\u once,因为once\u标志的存储开销 这个“LazyArray”是为了减少内存而编写的,目的是用对客户机代码的最小更改来替换数组(只需要访问元素的一小部分)。从多个线程访问阵列是一个事实,而且由于性能的原因,锁定可能会有问题 /** *数组元素的惰性创建。 *只有在引用它们

这段代码是否使用C++11原子安全地实现了双重检查习惯用法? 我在《C++编程语言第四版》中看到了一个例子,使用了<代码>原子< /代码>,我尽我所能保持事物的等价性,但我没有信心。还有,这方面可以改进吗

我希望避免
call\u once
,因为
once\u标志的存储开销

这个“LazyArray”是为了减少内存而编写的,目的是用对客户机代码的最小更改来替换数组(只需要访问元素的一小部分)。从多个线程访问阵列是一个事实,而且由于性能的原因,锁定可能会有问题

/**
*数组元素的惰性创建。
*只有在引用它们时才会创建它们。
*
*这个“数组”不支持迭代,因为它可能有洞
*
*未检查数组边界以保持快速。
*销毁阵列会销毁所有T对象(通过唯一的任务器)
*/
模板
类懒散数组
{
typedef懒散数组mytype;
公众:
//不允许复制(与常规数组不同)
LazyArray(constlazyarray&)=delete;
LazyArray&运算符=(const LazyArray&)=删除;
LazyArray(){}
T&T操作员[](尺寸i)
{
返回(i);
}
常量和运算符[](大小i)常量
{
返回const_cast(本)->at(i);
}
私人:
使用防护装置=标准::锁定防护装置;
//通过引用获取索引i处的T对象
T&at(size\u T i)//只实现了非常量变量,常量版本将使用常量转换
{
auto&p=m_数组[i];
std::原子ap(p.get());
if(!ap)//尚未创建对象
{
防护罩g(mtx);
ap=p.get();
如果(!ap)
p、 重置(新T);
}
返回*p;
}
std::unique_ptr m_数组[大小];
std::互斥mtx;
};

理论上没有。

支票:

        std::atomic<T*> ap(p.get());
        if(!ap) // object not created yet
事实并非如此

它如何失败:

  • 对象构造new T的某些部分在另一个线程看到p被分配了新值后可能对其可见
  • 赋值p.reset可能会被撕毁,因为它是非原子的
  • 可能在某些平台上。

  • 如果存储可以在其他存储之后重新排序(在x86上不会发生),或者如果编译器决定交换这些存储本身(不太可能),则会发生这种情况
  • 指针大小的变量写入不会被破坏

  • 使用
    std::atomic
    修复这两个问题:

       std::atomic<T*> m_array[size];
    
    std::原子m_数组[size];
    
    确定您必须手动删除T*。
    我建议基于
    std::atomic
    创建
    atomic\u unique\u ptr
    ,并在那里使用它

    记住,只有当
    sizeof(T)
    远大于
    sizeof(unique\u ptr)
    (通常是指针的大小,例如8字节)时,这才有意义。您已经有一个
    大小
    唯一的\u ptr对象数组,占用了空间。引入另一级别的间接寻址也不利于性能,并且如果您对数组的顺序元素进行迭代,可能会导致更糟糕的局部性。理想情况下,您可以直接为
    T
    数组分配连续内存,但不要触摸它,这样操作系统的延迟分配机制就可以为您发挥作用(将来自操作系统的新虚拟内存页惰性地保留为零/COW映射到操作系统的零页)直到第一次读取或写入。但是,您需要知道何时使用placement new通过单独的簿记或T中的sentinel字段来构造新元素。也就是说,知道T的某些部分不能在已构造的对象中
    0
    。您应该使用const one和
    const cast
    来实现非const函数>,而不是相反。非常量函数丢弃常量是可以的。当前代码中的问题是由于通过常量函数中的
    const_cast
    修改成员而导致行为未定义。@Darhuuk,在这种情况下,可能应该删除常量变量,因为如果LazyArray是常量,则意味着每个
    unique_ptr
    也是常量,不能安全重置吗?没有常量调用非常量变量,都调用
    at()
    @cplupuzzle我的观点是,您的常量函数修改类的成员(在本例中是互斥体,在非常量函数
    at
    中)。通常不会编译。因为
    const_cast
    它会编译,但现在它是未定义的行为。感谢@Alex。难道不能保证
    p
    只有在
    t
    对象完全构造后才能获得新
    t
    的地址吗?@cplupuzzle,否。可以在其他商店之后订购商店。在平台上如ARM,它可能由于硬件行为而发生。请参阅下表:它可能由于编译器优化而假设发生,但可能性不大,请参阅“好像下的优化”。好的,谢谢@Alex。可以保证每个数组索引只创建一个对象,并且如果数组元素是纯数据,则无法访问无效内存,这是正确的吗?只有一个对象-是。无法访问无效内存-否,这是由于上述重新排序。纯数据减少了实际访问内存的机会将发生,但不会消除理论上的可能性。例如,debug
    new
    可能会初始化为某种模式,并且此初始化可能会移过
    p
    的赋值。
       std::atomic<T*> m_array[size];