C++ vptr上的数据竞争是否明显非法?

C++ vptr上的数据竞争是否明显非法?,c++,constructor,thread-safety,language-lawyer,vptr,C++,Constructor,Thread Safety,Language Lawyer,Vptr,在继续之前,请注意:这纯粹是一个语言问题。我希望得到基于标准引用的答案。我不是在寻找编写C++代码的建议。请像我是一个编译器作者一样回答 在构造仅包含独占子对象(#)的对象期间,尤其是那些仅包含非虚拟基的对象(也包括仅命名一次虚拟基类的对象),引用基类子对象的左值的动态类型“增加”:它从基类的类型变为运行构造函数的类的类型 (#)如果子对象恰好是另一个对象(可能是另一个子对象或完整对象)的直接子对象,则该子对象是独占的。成员和非虚拟基始终是独占的 在销毁过程中,类型会减少(直到该子对象的析构函数

在继续之前,请注意:这纯粹是一个语言问题。我希望得到基于标准引用的答案。我不是在寻找编写C++代码的建议。请像我是一个编译器作者一样回答

在构造仅包含独占子对象(#)的对象期间,尤其是那些仅包含非虚拟基的对象(也包括仅命名一次虚拟基类的对象),引用基类子对象的左值的动态类型“增加”:它从基类的类型变为运行构造函数的类的类型

(#)如果子对象恰好是另一个对象(可能是另一个子对象或完整对象)的直接子对象,则该子对象是独占的。成员和非虚拟基始终是独占的

在销毁过程中,类型会减少(直到该子对象的析构函数体的末尾,此时该子对象已消失且不再具有动态类型)

[在构造具有共享基类子对象的对象期间(即在具有至少具有虚拟基的不同基子对象的类中),基子对象的动态类型可能会暂时“消失”。我不希望在这里讨论此类类。]

真正的问题是:如果对象的动态类型在另一个线程中增加,会发生什么情况?

问题的标题是标准C++问题,用非标准术语(VPTR)表示,这可能看起来矛盾。原因是:

  • 没有要求根据vptr实现多态性,但(几乎?)总是这样。对象中的一个(或多个)vptr表示多态对象的动态类型
  • 数据竞争是根据对内存位置的读/写操作来定义的
  • 标准文本通常使用“仅用于说明”的非标准元素来定义标准特征。(那么,为什么不使用vptr“仅用于展示”?)
本标准未将多态对象(*)的行为直接定义为其动态类型的函数;该标准规定了在所谓的“生存期”(构造函数完成后)内,在派生类型最多的构造函数的主体内(使用相同语义允许完全相同的表达式),以及在基类子对象构造函数内,允许使用哪些表达式

(*)多态或动态对象的动态行为(**)包括:派生到基转换的虚拟调用、向下转换(
static_-cast
Dynamic_-cast
)、多态对象的
typeid

(**)动态对象是指其类使用virtual关键字的对象;由于这个原因,它的构造函数不是微不足道的

所以描述是这样的:在某件事完成之后,在某件事开始之前,在其他事情之前,等等。一些表达是有效的,并且做这样那样的事情

<> P>构造和销毁规范是在线程是标准C++的一部分之前编写的。那么线程的标准化带来了什么变化呢?有一句话定义了线程行为(规范部分):

在本款中,“之前”和“之后”指的是“发生在之前” 关系([intro.multithread])

因此,很明显,如果构造函数调用的完成和对象的使用之间存在先发生后发生的关系,并且对象的使用和析构函数的调用之间存在先发生后发生的关系(如果调用了),那么对象被视为完全构造的

但它没有说明在构造基类子对象之后,在构造派生类的过程中会发生什么:显然,如果任何动态属性用于构造中的多态对象,则存在竞争条件,但竞争条件并不非法

[竞态条件是非确定性的情况,任何有意义地使用互斥、条件变量、RWLOCK、大量使用信号量、大量使用其他同步设备以及所有使用原子原语的情况都会至少在原子对象上的修改顺序级别引入竞态条件。该低级别的非确定性m不可预测的高级行为的结果取决于原语的使用方式。]

然后,标准草案接着说:

[ 注意:因此,如果对象 在一个线程中构造的对象是从另一个线程引用的 没有足够的同步- 尾注 ]

哪里定义了“充分同步”?

缺乏“充分的同步”在道德上是否等同于常规的数据竞争:vptr上的数据竞争,或者用标准的话说,动态类型上的数据竞争

为简单起见,我希望将问题的范围限制在单个继承上,至少作为第一步。(无论如何,该标准对于具有多个继承的对象的构造非常混淆。)

这是语言律师问题所以我对以下内容不感兴趣:

  • 使用正在另一个线程中构造的对象是否可取(可能不可取)
  • 如何使用同步来可靠地修复竞争条件
  • 编译器供应商是否希望支持这种用例(他们可能不支持,也不会支持)
  • 这是否可能在任何现实世界的实现中可靠地工作(在当前实现的非平凡情况下可能无法可靠地工作)
编辑:上一个例子没有说明问题,而是分散了注意力。它在聊天区引发了一场非常有趣但完全无关的讨论

以下是一个不会导致相同问题的更清晰的示例:

atomic<Base1*> shared;

struct Base1 {
  virtual void f() {}
};

struct Base2 : Base1 {
  virtual void f() {}
  Base2 () { shared = (Base1*)this; }
};

struct Der2 : Base2 {
  virtual void f() {}
};

void use_shared() {
  Base1 *p;
  while (! (p = shared.get()));
  p->f();
}
消费者/生产者逻辑:

  • 线程A:
    new Der;
  • 苏氨酸
    atomic<Base*> shared;
    
    struct Base {
      virtual void f() {}
      Base () { shared = this; }
    };
    
    struct Der : Base {
      virtual void f() {}
    };
    
    void use_shared() {
      Base *p;
      while (! (p = shared.get()));
      p->f();
    }