只有带有琐碎析构函数的类型才适合存储新放置的类型吗?

时间:2018-07-02 08:24:05

标签: destructor c++17 placement-new

放置new的示例通常使用无符号char数组作为基础存储。步骤可以是:

  1. 使用new创建未签名的char数组
  2. 在此存储中创建一个对象,放置新位置
  3. 使用对象
  4. 破坏对象
  5. 为未签名的char数组调用delte以释放该数组

第5点似乎只有在我们为带有微不足道的析构函数的基础存储使用类型时才起作用。否则,我们将调用基础存储类型的析构函数,但其​​中不存在任何对象。从技术上讲,我们正在销毁一堆不存在的无符号字符,我们很幸运,无符号字符类型的破坏者是微不足道的,所以没有操作。

下面的代码如何?

struct A{ /* some members... */ };
struct B{ /* some members... B shall be same size as A */ };

int main()
{    
    auto ptr_to_a = new A; // A object lives @ ptr_to_a    
    ptr_to_a->~A(); // A object destroyed. no object living @ ptr_to_a, but storage is preserved
    new (ptr_to_a) B; // B object living @ ptr_to_a.    
    std::launder(reinterpret_cast<b*>(ptr_to_a))->/*...*/; // use B. for this purpose we need std::launder in C++17 or we would store the pointer returned by the placement new and use it without std::launder
    std::launder(reinterpret_cast<b*>(ptr_to_a))->~B(); // B object destroyed. no object living @ ptr_to_a, but storage is preserved

    // at this point there is no object living @ ptr_to_a, but we need to hand back the occupied storage.

    // a)
    delete ptr_to_a; // undefined behavior because no object is sitting @ ptr_to_a

    // b)
    new (ptr_to_a) A; // create an object again to make behavior defined. but this seems odd.
    delete ptr_to_a;

    // c)
    // some method to just free the memory somehow without invoking destructors?

    return 0;
}

https://en.cppreference.com/w/cpp/language/lifetime上写道: 作为一种特殊情况,如果...,可以在无符号char或std :: byte数组中创建对象(在这种情况下,据说该数组为对象提供了存储)。

这是否意味着只允许在无符号char和byte数组上使用new放置,并且因为它们具有琐碎的析构函数,我的代码示例已过时?

否则,我的代码样本如何?选项b)是唯一有效的解决方案吗?

编辑:第二例:

struct A{ /* some members... */ };
struct alignas(alignof(A)) B{ /* some members... */ };

int main()
{    
    static_assert(sizeof(A) == sizeof(B));
    A a;      
    a.~A();    
    auto b_ptr = new (&a) B; 
    b_ptr->~B();    
    return 0;
    // undefined behavior because a's destructor gets called but no A object is "alive" (assuming non trivial destructor)
    // to make it work, we need to placement new a new A into a?
}

2 个答案:

答案 0 :(得分:0)

通常,您将不需要使用不相关的类B的分配所返回的存储来放入int main () { char storage[sizeof(B)]; std::aligned_storage<sizeof(B), alignof(B)>::type aligned_storage; auto b_ptr1 = new (&storage) B; // potentially unaligned auto b_ptr2 = new (&aligned_storage) B; // guaranteed safe // use b_ptr1, b_ptr2 b_ptr1->~B(); b_ptr2->~B(); // storage ceases to exist when main returns } 。您甚至根本不需要分配

new

如果您确实需要动态分配,我建议将存储包装在holder结构中,以免结束struct B_holder { std::aligned_storage<sizeof(B), alignof(B)>::type storage; B * make_B() { return new(&storage) B; } } int main() { auto holder = std::make_unique<B_holder>(); auto * B_ptr = B_holder->make_B(); // use B_ptr B_ptr->~B(); // holder ceases to exist when main returns } 编辑的对象的生命期。

{{1}}

答案 1 :(得分:0)

在大多数情况下是

您可以执行以下操作。

struct placed {
  char stuff[100];
};
struct stupid {
  std::aligned_storage_t<sizeof(placed), alignof(placed)> data;
  ~stupid() {
    std::cout << "stupid gone\n";
  }
};

int main() {
  auto* pstupid = new stupid;
  auto* pplaced = ::new( (void*)&pstupid->data ) placed;
  pplaced->~placed();
  auto* pstupid2 = ::new( (void*)pstupid ) stupid;
  delete pstupid2;
}

但是,正如类型名称所暗示的那样,这是非常愚蠢的。如果没有很多我没有在上面提供的noexcept保证,要确保异常安全就非常困难。

我还不确定delete pstupid2是否合法,因为它是通过放置new而不是通过简单的新表达式创建的。