首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >是否允许显式调用析构函数,然后在具有固定生存期的变量上放置new?

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

Stack Overflow用户
提问于 2017-03-05 01:19:34
回答 2查看 1.1K关注 0票数 21

我知道显式调用析构函数会导致未定义的行为,因为双重析构函数调用,就像下面这样:

代码语言:javascript
复制
#include <vector>

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

但是,如果我们调用placement new来“复活”对象呢?

代码语言:javascript
复制
#include <vector>

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

更正式地说:

  1. 在C++中会发生什么(我对C++03和C++11都感兴趣,如果有区别的话),如果我显式地调用某个对象的析构函数,而这个对象最初不是用placement new构造的(例如,它要么是局部/全局变量,要么是用new分配的),然后,在这个对象被析构之前,对它调用placement new来“恢复”它?
  2. 如果它是ok的,那么是否保证对该对象的所有非常数引用也都是ok的?只要我在对象“死”的时候不使用它们?
  3. 如果是这样,是否可以使用一个非常数引用来放置新对象以复活对象?
  4. 常量引用怎么办?

示例用例(尽管这个问题更多的是关于好奇心):我想“重新分配”一个没有operator=的对象。

我见过this members,它说“覆盖”具有非静态const成员的对象是非法的。因此,让我们将这个问题的范围限制在没有任何const成员的对象上。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-03-05 02:01:24

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

其次,在退出其作用域之前,必须确保存在存储用于foo的原始类型的对象;因此,如果抛出任何异常,您必须捕获它并终止程序([basic.life]/9)。

总体而言,这个想法往往很诱人,但几乎总是一个可怕的想法。

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

代码语言:javascript
复制
- (8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and
- (8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
- (8.3) the type of the original object is not const-qualified, and, if a class type, does not contain any non-static   data member whose type is const-qualified or a reference type, and
- (8.4) the original object was a most derived object (1.8) of type   T and the new object is a most derived   object of type T (that is, they are not base class subobjects).

  • (9)如果程序使用静态(3.7.1)、线程(3.7.2)或自动(3.7.3)存储持续时间结束类型为T的对象的生命周期,并且如果T具有非平凡的析构函数,则程序必须确保隐式析构函数调用发生时,原始类型的对象占用相同的存储位置;否则程序的行为是未定义的。即使在出现异常的情况下退出块也是如此。

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

如果你真的,真的想重新分配一个对象,找到一个std::optional实现,并使用它来创建/销毁对象;这是非常小心的,而你几乎肯定不会足够小心。

票数 14
EN

Stack Overflow用户

发布于 2017-03-05 01:52:48

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

下面是一个演示此行为的示例程序(Ideone link):

代码语言:javascript
复制
#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

错误:构造函数失败

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42598915

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档