是否允许显式调用析构函数,然后将新变量放置在具有固定生命周期的变量上? [英] Is it allowed to call destructor explicitly followed by placement new on a variable with fixed lifetime?

查看:92
本文介绍了是否允许显式调用析构函数,然后将新变量放置在具有固定生命周期的变量上?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道由于双重析构函数的调用,显式调用析构函数会导致未定义的行为,例如:

#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. 如果我显式调用某个对象的析构函数,而该对象在第一个布局中未使用new构造,则在C ++中会发生什么(如果有区别,我会对C ++ 03和C ++ 11都感兴趣)放置(例如,它是本地/全局变量或已分配了new),然后在销毁该对象之前,对其调用new放置以恢复"它吗?
  2. 如果还可以,只要我在对象死"时不使用它们,是否可以保证对该对象的所有非常量引用都可以?
  3. 如果是这样,可以使用非常量引用之一进行新放置以使对象复活吗?
  4. const引用如何?

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

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

解决方案

首先,[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= 之类的简单事物不是其中之一.

如果您确实要重新分配对象,请找到一个std::optional实现,并使用该对象创建/销毁对象;这很小心,您几乎肯定不会足够小心.

I know that calling destructor explicitly can lead to undefined behavior because of double destructor calling, like here:

#include <vector>

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

But, what if we call placement new to "resurrect" the object?

#include <vector>

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

More formally:

  1. What will happen in C++ (I'm interested in both C++03 and C++11, if there is a difference) if I explicitly call a destructor on some object which was not constructed with placement new in the first place (e.g. it's either local/global variable or was allocated with new) and then, before this object is destructed, call placement new on it to "restore" it?
  2. If it's ok, is it guaranteed that all non-const references to that object will also be ok, as long as I don't use them while the object is "dead"?
  3. If so, is it ok to use one of non-const references for placement new to resurrect the object?
  4. What about const references?

Example usecase (though this question is more about curiosity): I want to "re-assign" an object which does not have operator=.

I've seen this question which says that "overriding" object which has non-static const members is illegal. So, let's limit scope of this question to objects which do not have any const members.

解决方案

First, [basic.life]/8 clearly states that any pointers or references to the original foo shall refer to the new object you construct at foo in your case. In addition, the name foo will refer to the new object constructed there (also [basic.life]/8).

Second, you must ensure that there is an object of the original type the storage used for foo before exiting its scope; so if anything throws, you must catch it and terminate your program ([basic.life]/9).

Overall, this idea is often tempting, but almost always a horrible idea.

  • (8) If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

    • (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) If a program ends the lifetime of an object of type T with static (3.7.1), thread (3.7.2), or automatic (3.7.3) storage duration and if T has a non-trivial destructor, the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined. This is true even if the block is exited with an exception.

There are reasons to manually run destructors and do placement new. Something as simple as operator= is not one of them, unless you are writing your own variant/any/vector or similar type.

If you really, really want to reassign an object, find a std::optional implementation, and create/destroy objects using that; it is careful, and you almost certainly won't be careful enough.

这篇关于是否允许显式调用析构函数,然后将新变量放置在具有固定生命周期的变量上?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆