【发布时间】:2020-09-28 23:03:22
【问题描述】:
首先,我知道从构造函数/析构函数中调用虚函数是一种不好的做法。 然而,这样做的行为,尽管可能令人困惑或不是用户所期望的,但仍然是明确定义的。
struct Base
{
Base()
{
Foo();
}
virtual ~Base() = default;
virtual void Foo() const
{
std::cout << "Base" << std::endl;
}
};
struct Derived : public Base
{
virtual void Foo() const
{
std::cout << "Derived" << std::endl;
}
};
int main(int argc, char** argv)
{
Base base;
Derived derived;
return 0;
}
Output:
Base
Base
现在,回到我真正的问题。如果用户从不同线程的构造函数中调用虚函数会发生什么。有比赛条件吗?它是未定义的吗? 或者换句话说。编译器设置 vtable 是线程安全的吗?
例子:
struct Base
{
Base() :
future_(std::async(std::launch::async, [this] { Foo(); }))
{
}
virtual ~Base() = default;
virtual void Foo() const
{
std::cout << "Base" << std::endl;
}
std::future<void> future_;
};
struct Derived : public Base
{
virtual void Foo() const
{
std::cout << "Derived" << std::endl;
}
};
int main(int argc, char** argv)
{
Base base;
Derived derived;
return 0;
}
Output:
?
【问题讨论】:
-
vtable 本身通常是一个静态结构,但是需要在对象内部设置 vtable 指针。但当然,这些都是不能依赖的实现细节。
-
我认为没有理由认为这是线程安全的。
-
由于异步函数的行为在构造函数结束时会发生变化,因此行为取决于该函数的时间与没有同步的构造函数的时间,这意味着必须存在竞争条件。这里肯定违反了语言规则,但我不确定是哪一个。它不能像对 vtable 指针的竞赛那样简单,因为语言无法识别该指针。这是一个实现细节。必须有更高的语言概念或要求被违反。
-
@Gils 首先,可能有许多其他原因导致 UB 不符合竞争条件。其次,vtable 和 vtable 指针的概念是实现细节,语言标准没有提及或要求。所以它不可能明确地保证与 vtables 相关的任何事情。关于 vtables 的保证必须从关于多态性的规则和其他规则中推断出来。
-
@Gils 这是你的规则:
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.除非你能找到一条规则说类对象的构造是原子的,否则你就违反了这条规则。
标签: c++ language-lawyer race-condition object-lifetime