【发布时间】:2020-06-27 23:28:44
【问题描述】:
这是你不应该做的场景,但https://timsong-cpp.github.io/cppwp/class.cdtor#4 声明:
成员函数,包括虚函数 ([class.virtual]),可以在构造或销毁 ([class.base.init]) 期间调用。
如果函数被并行调用,这是否成立?也就是说,忽略竞争条件,如果 A 处于构造中间,并且在调用构造函数之后的某个时间点(例如在构造期间)调用了 frobme,那仍然是定义的行为吗?
#include <thread>
struct A {
void frobme() {}
};
int main() {
char mem[sizeof(A)];
auto t1 = std::thread([mem]() mutable { new(mem) A; });
auto t2 = std::thread([mem]() mutable { reinterpret_cast<A*>(mem)->frobme(); });
t1.join();
t2.join();
}
作为一个单独的场景,也有人向我指出,A 的构造函数可以创建多个线程,这些线程可能会在 A 完成构造之前调用成员函数函数,但是这些操作的顺序将更易于分析(您知道在构造函数中生成线程之前不会发生竞争)。
【问题讨论】:
-
“忽略竞争条件”?竞争条件是问题的核心。如果
t2首先运行,则没有对象可以调用成员函数 -
是的,这是一个巨大的危险信号。我想我在想,如果有一些神奇的同步原语,你可以确保
frobme在构造函数的第一条指令之后执行,那会被认为是未定义的行为。这是从静态分析的角度来的。 -
可能在构造函数初始化列表中锁定/释放互斥锁的一些技巧可以确保构造开始但在调用成员函数时尚未完成
-
这段代码还有一个问题——
reinterpret_cast<A*>(mem)不能保证指向由placement-new 创建的对象。您应该使用placement-new“返回”的指针。此外,mem可能与A不一致,但我想您为了简洁而省略了该细节,因此我们可以忽略这一点。 -
@M.M 在平面架构上,指针不包含地址以外的任何信息。所以
reinterpret_cast<A*>(mem)可以,即使标准委员会喜欢假装它不是。这是一个很难解决的重大性病问题。
标签: c++ constructor language-lawyer race-condition object-lifetime