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

yep*_*ons 20 c++ lifetime placement-new c++11 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.
}
Run Code Online (Sandbox Code Playgroud)

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

#include <vector>

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

更正式的:

  1. 如果我在某个对象上显式调用析构函数而不是首先使用placement new构造,那么C++中会发生什么(我对C++ 03和C++ 11都感兴趣,如果存在差异)(例如它是本地/全局变量还是被分配了new)然后,在该对象被破坏之前,在其上调用placement new以"恢复"它?
  2. 如果没关系,是否保证对该对象的所有非const引用也都可以,只要我在对象"死"时不使用它们?
  3. 如果是这样,是否可以使用非const引用中的一个用于放置new以恢复对象?
  4. const引用怎么样?

示例用例(虽然这个问题更多是关于好奇心):我想"重新分配"一个没有的对象operator=.

我已经看到这个问题,说"覆盖"具有非静态const成员的对象是非法的.所以,让我们将这个问题的范围限制在没有任何const成员的对象上.

Yak*_*ont 13

首先,[basic.life]/8明确指出对原始的任何指针或引用foo都应引用您foo在案例中构造的新对象.另外,名称foo将引用那里构造的新对象(也[basic.life]/8).

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

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

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

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

有理由手动运行析构函数并进行新的放置.除非您正在编写自己的变体/任何/矢量或类似类型,否则一样简单operator= 就不是其中之一.

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


Geo*_*ard 7

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

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

#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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这打印:

构建于0x7fff41ebf03f

在0x7fff41ebf03f处被摧毁

在0x7fff41ebf03f处被摧毁

错误:构造函数失败