显式析构函数调用后,此赋值是无效的吗?

时间:2013-03-12 11:15:54

标签: c++

我遇到了一些代码,用于在不重新分配内存的情况下就地替换对象:

static void move(void* const* src, void** dest) {
   (*reinterpret_cast<T**>(dest))->~T();
   **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
}

这对我看起来像UB,因为对象被销毁然后被分配给而没有被构造,即它需要只是复制 - 分配(仅第二行)或明确地破坏(第一行),然后是放置 - 新的复制结构而不是作业。

我只是问,因为虽然这对我来说似乎是一个明显的错误,但它在boost::spirit::hold_any和它所基于的原始cdiggins::any中已经存在了一段时间。 (我在Boost开发者邮件列表中询问过这个问题,但是等待响应时希望在本地解决这个问题,如果确实不正确的话。)

2 个答案:

答案 0 :(得分:3)

假设reinterpret_cast是明确定义的(即dest确实是指向T的指针),标准将对象生命周期的结束定义为:< / p>

  

类型T的对象的生命周期在以下时间结束:

     
      
  • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始,或
  •   
  • 对象占用的存储空间被重用或释放。
  •   

然后对glvalue **reinterpret_cast<T**>(dest)

可以做些什么进行了一些限制
  

类似地,在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,可以使用引用原始对象的任何glvalue,但是仅以有限的方式使用。 [...]如果出现以下情况,该程序的行为未定义:

     
      
  • 左值到左值的转换(4.1)适用于这样的glvalue,
  •   
  • glvalue用于访问非静态数据成员或调用对象的非静态成员函数,或
  •   
  • glvalue被隐式转换(4.10)为对基类类型的引用,或
  •   
  • glvalue用作static_cast(5.2.9)的操作数,除非转换最终是cv char&amp;或cv unsigned char&amp;,或
  •   
  • glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。
  •   

强调补充。

如果对象没有在这个终生状态中结束,因为它有一个简单的析构函数,那就没有问题了。但是,对于具有非平凡析构函数的类类型的任何T,我们知道赋值运算符被视为该类的成员函数operator=。通过此glvalue调用对象的非静态成员函数会导致未定义的行为。

答案 1 :(得分:0)

  

这看起来像UB,因为对象被破坏然后   分配到没有被构造,即它需要正义   copy-assign(仅限第二行)或显式销毁(第一行)   line)后面是placement-new copy构造而不是   分配

没有必要修改任何东西,虽然这个代码在没有进一步限定的情况下肯定是不安全的(虽然它在使用它的情况下肯定是安全的。)

dest处的对象被销毁,然后将src处对象支持的内存复制到dest以前的对象所在的位置。最终结果:你已经销毁了一个对象并放置了另一个对象的浅层克隆,而第一个对象曾经存在。

如果您只进行复制分配,则第一个对象不会被破坏,导致资源泄漏。

使用展示位置new填充dest的内存是一个选项,但它的语义与现有代码的语义非常不同(创建一个全新的对象,而不是对现有内容进行浅层克隆)一)。 Placement new和使用复制构造函数也有不同的语义:对象需要有一个可访问的复制构造函数,你不再控制结果(复制构造函数会做任何想做的事情)。