是否允许显式调用析构函数,然后在具有固定生命期的变量上放置new?

时间:2017-03-04 17:19:34

标签: c++ c++11 lifetime placement-new explicit-destructor-call

我知道显式调用析构函数会因为双析构函数调用而导致未定义的行为,如下所示:

#include <vector>

int main() {
  std::vector<int> foo(10);
  foo.~vector<int>();
  return 0;  // Oops, destructor will be called again on return, double-free.
}

但是,如果我们将新位置称为“复活”对象,该怎么办?

#include <vector>

int main() {
  std::vector<int> foo(10);
  foo.~vector<int>();
  new (&foo) std::vector<int>(5);
  return 0;
}

更正式:

  1. 如果我在第一个没有使用placement new构造的对象上显式调用析构函数,那么在C ++中会发生什么(我对C ++ 03和C ++ 11都感兴趣,如果有区别的话) place(例如它是本地/全局变量或者是new分配的)然后,在此对象被破坏之前,在其上调用placement new以“恢复”它?
  2. 如果没问题,是否保证对该对象的所有非const引用也都可以,只要我在对象“死”时不使用它们?
  3. 如果是这样,是否可以使用非const引用之一来放置new来复活对象?
  4. const引用怎么样?
  5. 示例用例(虽然这个问题更多是关于好奇心):我想“重新分配”一个没有operator=的对象。

    我看过this问题,其中说“具有非静态const成员的”覆盖“对象是非法的。因此,我们将此问题的范围限制为没有任何const成员的对象。

2 个答案:

答案 0 :(得分:13)

首先,[basic.life]/8清楚地指出,对原始foo的任何指针或引用都将引用您在foo中构造的新对象。此外,名称foo将引用在那里构建的新对象(也是[basic.life]/8)。

其次,您必须确保在退出其范围之前存储用于foo的存储的原始类型的对象;所以如果抛出任何东西,你必须抓住它并终止你的程序([basic.life]/9)。

总的来说,这个想法往往很诱人,但几乎总是一个可怕的想法。

  
      
  • (8)如果在对象的生命周期结束之后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建一个新对象,指针指向原始对象,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操纵新对象,如果:

         
        
    • (8.1)新对象的存储完全覆盖原始对象占用的存储位置,
    •   
    • (8.2)新对象与原始对象的类型相同(忽略顶级cv限定符),
    •   
    • (8.3)原始对象的类型不是const限定的,如果是类类型,则不包含任何非静态类型   类型为const限定的数据成员或引用类型,
    •   
    • (8.4)原始对象是类型最派生的对象(1.8)   T和新对象是派生最多的   类型为T的对象(也就是说,它们不是基类子对象)。
    •   
  •   
  • (9)如果程序以静态(3.7.1),线程(3.7.2)或自动(3.7.3)存储持续时间结束类型为T的对象的生命周期,并且如果T具有非平凡的析构函数,程序必须确保一个对象的   当隐式析构函数调用发生时,原始类型占用相同的存储位置;否则程序的行为是不确定的。即使该块以异常退出,也是如此。

  •   

有理由手动运行析构函数并进行新的放置。像operator= 这样简单的东西不是其中之一,除非您正在编写自己的变体/ any / vector或类似类型。

如果你真的想要重新分配一个对象,找一个std::optional实现,然后使用它创建/销毁对象;它很小心,你几乎肯定不够小心。

答案 1 :(得分:7)

这不是一个好主意,因为如果新对象的构造函数抛出异常,您仍然可以最终运行析构函数两次。也就是说,析构函数将始终在作用域的末尾运行,即使您以特殊方式离开作用域。

以下是展示此行为的示例程序(Ideone link):

#include <iostream>
#include <stdexcept>
using namespace std;

struct Foo
{
    Foo(bool should_throw) {
        if(should_throw)
            throw std::logic_error("Constructor failed");
        cout << "Constructed at " << this << endl;
    }
    ~Foo() {
        cout << "Destroyed at " << this << endl;
    }
};

void double_free_anyway()
{
    Foo f(false);
    f.~Foo();

    // This constructor will throw, so the object is not considered constructed.
    new (&f) Foo(true);

    // The compiler re-destroys the old value at the end of the scope.
}

int main() {
    try {
        double_free_anyway();
    } catch(std::logic_error& e) {
        cout << "Error: " << e.what();
    }
}

打印:

  

构造于0x7fff41ebf03f

     

在0x7fff41ebf03f

处被摧毁      

在0x7fff41ebf03f

处被摧毁      

错误:构造函数失败