可以在调用构造函数之前完成赋值吗?

Posted

技术标签:

【中文标题】可以在调用构造函数之前完成赋值吗?【英文标题】:Can assignment be done before constructor is called? 【发布时间】:2009-06-17 21:46:09 【问题描述】:

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

【问题讨论】:

没有什么可以对付它,真的。您将必须使任何读取和任何写入变得易失,并且必须使“内存”也变得易失,以保持代码写入的顺序,并让所有这些写入/读取由序列点分隔。但这仍然不会对多线程做任何事情:标准对此一无所知。 如“int volatile a, b; a = 1; b = 2;”这两个不能重新排序,并且在“int volatile a; int b; a = 1; b = 2;”中那么这些 can 可以重新排序,因为 b 不是易变的。而在 f(a=1, b=2);然后这些也可以“重新排序”,因为一开始就没有任何顺序。我发现 alexandrescu 和 scott meyers 的文章很好地解释了这一点,恕我直言 见:常见的未定义行为***.com/questions/367633/… 【参考方案1】:

问题不在于代码执行时,而更多地与写入顺序有关。

假设:

A()

   member = 7;

然后:

singleton = new A()

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

【讨论】:

【参考方案2】:

我认为以下应该可行:

void Test()

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

【讨论】:

【参考方案3】:

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.

写出这样的东西的危险在于,你隐式添加了许多原始序列中不存在的序列点,此外,编译器被允许生成看起来不像这样长的代码因为它的行为“好像”它是由执行程序可以确定的 this 编写的。这个“好像”规则仅适用于正在执行的线程,因为(当前)语言没有说明与其他线程的交互是否有效。

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

【讨论】:

您的回答是基于我的包含错字(缺少 *)的代码。抱歉,代码已修复。【参考方案4】:

是的,可以在赋值后调用构造函数,尽管您给出的示例在内部不一致(正如评论所指出的那样)。

您可以装上一些锁以让您安心,但也很容易弄错。

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

Scott Meyers 和 Andrei Alexandrescu

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

【讨论】:

以上是关于可以在调用构造函数之前完成赋值吗?的主要内容,如果未能解决你的问题,请参考以下文章

从复制构造函数调用默认赋值运算符是不好的形式吗?

基类构造函数真的在派生类构造函数之前调用吗

在复制构造函数中调用赋值运算符

我可以从 Main 构造函数中关闭程序吗?

c#中泛型类构造函数重载赋值时为啥不接受null?对其赋空值应给怎么做?

[QT入门篇]3 QObject的拷贝构造函数与赋值运算符