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

Mik*_*Lui 5 c++ constructor race-condition object-lifetime language-lawyer

这是你不应该做的场景,但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();
}
Run Code Online (Sandbox Code Playgroud)

作为一个单独的场景,有人还向我指出, 的A构造函数可以创建多个线程,这些线程可能会A在构造完成之前调用成员函数函数,但是这些操作的顺序会更易于分析(您知道在构造函数中生成线程之后才会发生竞争)。

Nic*_*las 6

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

在您的特定代码中,即使在最好的情况下(在t2之后执行t1),由于创建和使用之间缺乏同步,您也会遇到数据争用。这使得你的代码无论执行顺序如何都是UB。

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

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

假设这两种情况都不是,那么一切都应该没问题(大多数情况下。可以以A这样的方式定义,即您reinterpret_cast不会返回指向A您在该存储中创建的可用指针;您需要launder)。可以访问正在构造/销毁的对象,但只能以某些方式进行。坚持这些方法,你应该没问题……可能会有一个问题。

标准中没有关于对象初始化完成时的数据竞争的内容,仅涉及内存位置的冲突。一旦对象完全构造完毕,函数的行为virtual可能会根据 vtable 指针的变化而改变,如果动态类型是从给予其他线程的类派生的类的话。我不认为对象模型部分对此有明确的说明。

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

在构造对象期间,如果通过不是直接或间接从构造函数指针获取的泛左值来访问该对象或其任何子对象的值this,则由此获取的对象或子对象的值是未指定的。