c++ 使用对象提供的存储超出其析构是未定义的行为吗?

kfgdxczn  于 2023-03-25  发布在  其他
关注(0)|答案(3)|浏览(92)

C++标准是否允许使用对象在析构之后但在解除分配之前提供的存储?
很明显,在销毁一个常规对象后,存储空间可以被重用。但是在下面的例子中,对象B为定义为模板参数T的对象提供存储空间,并在自己的构造过程中在提供的存储空间中构造该对象。
此外,当B对象被销毁时,所提供的存储中的对象不会被销毁,并且用于提供存储的数组的生命周期不会结束,因为它有一个无效的平凡析构函数,这导致了A类型的对象的生命周期是否以B结束的问题。

#include <cstddef>
#include <iostream>
#include <memory>
#include <new>

struct A
{
  int i = 23;
};

template<class T>
struct B
{
  B()
  {
    // construct object T in the provided storage
    new (&c) T;
  }

  // return a reference to the object in the provided storage
  T & t()
  {
    return *std::launder(reinterpret_cast<T *>(&c));
  }

  // reserved storage
  alignas(T) std::byte c[sizeof(T)];
};

int
main()
{
  using Alloc = std::allocator<B<A>>;
  using AllocTraits = std::allocator_traits<Alloc>;
  Alloc alloc;

  // storage duration starts
  B<A> * b = AllocTraits::allocate(alloc, 1u);

  // construction of both B and A
  AllocTraits::construct(alloc, b);

  // Get a reference to A
  A & a = b->t();

  std::cout << "At this point A is alive. a.i = " << a.i << std::endl;
  a.i = 42;

  // object of type B is destroyed, but A is not
  AllocTraits::destroy(alloc, b);

  // Is it undefined behaviour to read from 'a'?
  std::cout << "Is A still alive? a.i = " << a.i << std::endl;

  // A is destroyed
  a.~A();

  // storage duration ends
  AllocTraits::deallocate(alloc, b, 1u);

  return 0;
}

问:为什么会有人这么做?
答:它允许为弱指针实现一个侵入式控制块,而不会像https://github.com/john-plate/pntr中那样产生额外的开销。

dgtucam1

dgtucam11#

一旦运行析构函数(destroy就是这么做的),b所指向的位置就不再有对象了,只有存储空间。
此时,a是一个悬空引用,使用它就是UB。
对存储做一些事情并不是UB(例如,您可以使用placement new或allocator_traits::construct在那里创建一个新对象)。

gudnpqoy

gudnpqoy2#

由于owner的析构函数在owned子对象之前被调用,因此可能存在UB地雷。B::B中的指令序列必须被正确地否定。std::allocator::destroy可能只调用B::~B,但不能保证它不会写一些东西作为重新分配的准备。从而覆盖未销毁的A示例。如果在将来的版本中使用不同的分配器,销毁后的行为就会改变。我猜你最终会得到一个定时炸弹。
事实上,在加密上下文中,比如SSL,我会定制destroy方法,使用优化栅栏来强制加密器加密已删除的对象。

gwbalxhn

gwbalxhn3#

根据https://en.cppreference.com/w/cpp/language/lifetime
对象的生存期等于或嵌套在其存储的生存期内,请参见存储持续时间。
我认为当你销毁B的时候,c也被销毁了,它的寿命也就结束了。因为c是a的存储器,所以a的寿命也就结束了。
因此,在b被销毁后使用a确实应该是未定义的行为。

相关问题