对What's wrong with this fix for double checked locking?的评论说:
问题在于变量可能是 在构造函数运行之前分配 (或完成),而不是在对象之前 分配。
让我们考虑一下代码:
A *a;
void Test()
{
a = new A;
}
为了进行更正式的分析,让我们将a = new A分成几个操作:
void *mem = malloc(sizeof(A)); // Allocation
new(mem) A; // Constructor
a = reinterpret_cast<A *>(mem); // Assignment
上面引用的评论是否属实,如果是,是什么意思?在分配后可以执行构造函数吗?如果可以,由于MT的安全性,在需要保证订单时可以采取什么措施呢?
答案 0 :(得分:1)
a
是一个具有静态存储持续时间的全局对象,因此它将在main执行主体执行之前的某个预分配存储中初始化。假设对Test的调用不是某些静态对象构造怪异的结果,a
将在调用Test时完全构造。
a = new A;
这种稍微不寻常的赋值不会(仅)标准的复制赋值操作,因为您将指向A
的指针分配给a,而不是对象或引用。它是否实际编译以及它究竟调用的内容取决于A
是否具有指向A
的赋值运算符,或者是指向A
的指针可隐式转换的内容,还是{{1}有一个非显式构造函数,它接受指向A
的指针(或指向基类A
的指针)。
编辑后,你的代码做了一些不同的事情!
从概念上讲,它更像是这样:
A
这样写出来的危险就是你隐含地添加了许多原始序列中没有的序列点,此外编译器还允许生成看起来不那么长的代码因为它就像执行程序可以确定的那样“写得好”。这个“好像”规则仅适用于执行线程,因为(当前)语言没有说明与其他线程的交互是否有效。
为此,您需要使用实施提供的特定行为保证(如果有)。
答案 1 :(得分:1)
问题不在代码执行时,而是与写入顺序有关。
我们假设:
A()
{
member = 7;
}
然后:
singleton = new A()
这导致代码执行分配,写入内存(成员),然后写入另一个内存位置(单例)。一些CPU可以重新编写写入,这样在写入单例之后才能看到成员写入 - 本质上,在系统中其他CPU上运行的代码可能具有写入单例的内存视图,但成员是不
答案 2 :(得分:1)
我认为以下应该有效:
void Test()
{
A *temp = new A;
MemoryWriteBarrier(); // use whatever memory barrier your platform offers
a = temp;
}
答案 3 :(得分:-1)
是的,可以在赋值后调用构造函数,尽管您给出的示例内部不一致(如注释中所述)。
你可以放心使用一些锁,但也很容易出错。
见
“C ++和双重锁定的危险”
作者:Scott Meyers和Andrei Alexandrescu