可以在调用构造函数之前进行赋值吗?

发布于 2024-07-24 03:34:14 字数 605 浏览 7 评论 0原文

双重检查锁定修复有什么问题?<的评论/a> 说:

问题是变量可能是 在构造函数运行之前分配 (或完成),不在对象之前 已分配。

让我们考虑一下代码:

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安全而需要保证订单时,可以采取什么措施呢?

A comment to What's wrong with this fix for double checked locking? says:

The issue is that the variable may be
assigned before the constructor is run
(or completes), not before the object
is allocated.

Let us consider code:

A *a;

void Test()
{
    a = new A;
}

To allow for more formal analysis, let us split the a = new A into several operations:

void *mem = malloc(sizeof(A)); // Allocation
new(mem) A; // Constructor
a = reinterpret_cast<A *>(mem); // Assignment

Is the comment quoted above true, and if it is, in what sense? Can Constructor be executed after the Assignment? If it can, what can be done against it when guaranteed order is needed because of MT safety?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

岁月如刀 2024-07-31 03:34:14

问题不在于代码执行时,而在于写入顺序。

让我们假设:

A()
{
   member = 7;
}

然后后来:

singleton = new A()

这会导致代码执行分配、写入内存(成员),然后写入另一个内存位置(单例)。 某些 CPU 可以重新排序写入,使得对成员的写入直到写入单例之后才可见 - 本质上,系统中其他 CPU 上运行的代码可以查看写入单例的内存,但成员是不是。

The issue isn't so much when code executes, but more to do with write-ordering.

Let's suppose:

A()
{
   member = 7;
}

Then later:

singleton = new A()

This results in code that does an allocation, a write to memory (member), and then a write to another memory location (singleton). Some CPU's can re-order writes such that the write to member will not be visible until after the write to singleton - in essence, code running on other CPU's in the system could have a view of memory where singleton is written to, but member is not.

地狱即天堂 2024-07-31 03:34:14

我认为以下应该有效:

void Test()
{
    A *temp = new A;
    MemoryWriteBarrier(); // use whatever memory barrier your platform offers
    a = temp;
}

I think following should work:

void Test()
{
    A *temp = new A;
    MemoryWriteBarrier(); // use whatever memory barrier your platform offers
    a = temp;
}
笙痞 2024-07-31 03:34:14

a 是一个具有静态存储持续时间的全局对象,因此它将在 main 主体执行之前的某个时间在一些预分配的存储中进行初始化。 假设对 Test 的调用不是某些静态对象构造怪异的结果,则 a 将在调用 Test 时完全构造完毕。

a = new A;

这种稍微不寻常的分配不会(仅)是标准的复制分配操作,因为您将指向 A 的指针分配给 a,而不是对象或引用。 它是否真正编译以及它到底调用什么取决于 A 是否具有接受指向 A 的指针的赋值运算符,或者可从指向 A 的指针隐式转换的内容A 是否具有非显式构造函数,该构造函数采用指向 A 的指针(或指向 A 基类的指针) )。

编辑后,您的代码做了一些相当不同的事情!

从概念上讲,它的作用更像是这样:

A *tmpa;
void *mem = ::operator new( sizeof(A) ); // ( or possibly A::operator new )

try
{
    tmpa = new (mem) A; // placement new = default constructor call
}
catch (...)
{
    ::operator delete( mem );
    throw;
}

a = tmpa; // pointer assignment won't throw.

写出这样的东西的危险在于,您隐式添加了许多原始序列点中不存在的序列点,此外,编译器还可以生成不存在的代码。只要执行程序可以确定它的行为“好像”是由此编写的,那么它就不会看起来像这样。 这个“好像”规则仅适用于执行线程,因为(当前)语言没有提及与其他线程的交互工作。

为此,您需要使用您的实现提供的特定行为保证(如果有)。

a is a global object with static storage duration so it's going to be initialised in some pre-allocated storage sometime before the body of main get executed. Assuming that a call to Test isn't the result of some static object construction weirdness, a will be full constructed by the time Test is called.

a = new A;

This slightly unusual assignment isn't going to be (only) a standard copy assignment operation as you are assigning a pointer to A to a, not an object or reference. Whether it actually compiles and what exactly it calls depends on whether A has an assignment operator that takes a pointer to A, or something implicitly convertable from a pointer to A or whether A has an non-explicit constructor that takes a pointer to A (or a pointer to a base class of A).

Post edit, your code does something rather different!

Conceptually, it does something more like this:

A *tmpa;
void *mem = ::operator new( sizeof(A) ); // ( or possibly A::operator new )

try
{
    tmpa = new (mem) A; // placement new = default constructor call
}
catch (...)
{
    ::operator delete( mem );
    throw;
}

a = tmpa; // pointer assignment won't throw.

The peril with writing something out like this is that your implicitly adding a lot of sequence points that just aren't there in the original, and in addition the compiler is allowed to generate code that doesn't look like this so long as it behaves 'as if' it were written by this as far as the executing program could determine. This 'as if' rule only applies to the executing thread as the (current) language says nothing about interaction with other threads works.

For this you need to use the specific behaviour guarantees (if any) proided by your implementation.

少女净妖师 2024-07-31 03:34:14

是的,可以在赋值后调用构造函数,尽管您给出的示例在内部不一致(如注释所述)。

为了安心,您可以加一些锁,但也很容易出错。

请参阅

“C++ 和双重检查锁定的危险”

Scott Meyers 和 Andrei Alexandrescu 的

http:// www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

Yes, the constructor can be called after the assignment, although the example you gave is not internally consistent (As noted by a comment on it).

You can throw in some locks for peace of mind, but it's easy to get that wrong, too.

see

"C++ and the Perils of Double-Checked Locking"

by Scott Meyers and Andrei Alexandrescu

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文