运行 与类型的构造函数并行的单独线程中的成员函数是未定义的行为吗?

Is it undefined behavior to run a member function in a separate thread, in parallel to the type's constructor?

这是您永远不应该做的场景,但是 https://timsong-cpp.github.io/cppwp/class.cdtor#4 指出:

Member functions, including virtual functions ([class.virtual]), can be called during construction or destruction ([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 之前调用成员函数 function已完成构建,但这些操作的顺序将更易于分析(您知道在构造函数中生成线程之前不会发生竞争)。

除了竞争条件(您可能正在使用互斥锁或类似方式进行管理)之外,您还受到生命周期尚未开始的对象的通常限制,即:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways.

有关允许和不允许的操作的完整列表,请参阅 [basic.life]

特别是限制之一是

The program has undefined behavior if:

...

  • the glvalue is used to call a non-static member function of the object

这显然禁止你的例子。

还有 [class.cdtor] 说:

For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior

即使您同步到构建开始后触发的某些事件,this rule 也会禁止该代码:

During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified

这里有两个问题:您的具体代码和您的一般问题。

在您的特定代码中,即使在最好的情况下(t2t1 之后执行),由于创建和使用之间缺乏同步,您也会发生数据竞争。无论执行顺序如何,这都会使您的代码成为 UB。

在一般问题中,我们假设一个类型的构造函数将 this 指针交给其他线程,然后调用其上的函数,并且传递本身已正确同步。其他调用成员函数的线程是否会被视为数据竞争?

好吧,如果另一个线程调用一个函数来读取成员值或在交接点之后由构造函数写入的其他数据,或者如果构造函数访问成员或其他数据,那肯定会发生数据竞争被调用的成员函数写入的数据。也就是说,如果同时执行的代码之间没有数据竞争。

假设这两种情况都不是,那么一切都应该没问题(主要是。可以定义 A 的方式使您的 reinterpret_cast 不会 return一个指向您在该存储中创建的 A 的可用指针;). An object under construction/destruction can be accessed, but only in certain ways. 坚持这些方式,您应该没问题……有一个可能的问题。

标准中没有关于对象初始化完成时的数据竞争的任何内容,只有内存位置的冲突。一旦对象被完全构建,virtual 函数的行为可能会根据更改 vtable 指针而改变,如果动态类型是 class 派生自 class 给另一个线程.我不相信在对象模型部分对此有明确的陈述。

此外,请注意 C++20 为 class.cdtor 添加了一条特殊规则:

During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified.