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;
}
Run Code Online (Sandbox Code Playgroud)
问:为什么有人会这样做?
答:它将允许为弱指针实现侵入式控制块,而无需额外的开销,如https://github.com/john-plate/pntr中所做的那样。
一旦运行了析构函数(这就是它的destroy作用), 所指向的位置就不再有对象了b。只有存储空间。
此时,a是一个悬空引用,使用它的是 UB。
UB 不会对存储执行某些操作(例如,您可以使用放置 new 或 在那里创建一个新对象allocator_traits::construct)。