可以不在放置新分配的对象上调用析构函数吗?

时间:2016-12-29 18:35:34

标签: c++

说我有一个固定的内存缓冲区

char *buffer; 

使用展示位置

在该缓冲区中分配我的结构
struct S
{ 
    std::tuple<int, double, char> m_data; 
    auto getRecord() 
    { 
        return m_data;
    }
};

S *newS = new(buffer + offset)S; 

我知道我应该手动调用此类已分配项目的析构函数,但如果没有涉及bookeeping /资源管理可以省略这个吗?换句话说,如果使用缓冲区的类的析构函数没有执行任何操作(类似于上面的~S()),是否可以跳过此步骤?如果是这样的话,我可以重用缓冲区而不破坏以前的租户吗?

3 个答案:

答案 0 :(得分:14)

该标准在第3.8节[basic.life]中有一条规则涵盖了这一点:

  

程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期。 对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数;但是,如果没有显式调用析构函数或者如果没有使用delete-expression(5.3.5)来释放存储,那么不应该隐式调用析构函数,并且任何依赖于副作用的程序都会产生由析构函数具有未定义的行为

许多专家都认为&#34;取决于析构者产生的副作用&#34;太模糊了,没有用处。许多人将其解释为重言式意义&#34;如果程序在未评估析构函数副作用时具有未定义的行为,则无法调用析构函数会导致未定义的行为&#34;。见Observable behavior and undefined behavior -- What happens if I don't call a destructor?

如果你的类型有一个简单的析构函数(在你的例子中似乎是这种情况),那么调用它(或者没有调用它)没有任何效果 - 调用一个简单的析构函数甚至不会结束它的生命周期对象

  

类型o的对象T的生命周期在以下时间结束:

     
      
  • 如果T是具有非平凡析构函数的类类型,则析构函数调用将启动,或者
  •   
  • 释放对象占用的存储空间,或者由未嵌套在o内的对象重用。
  •   

也就是说,如果T没有非平凡的析构函数,那么结束对象o生命周期的唯一方法就是释放或重用其存储。

答案 1 :(得分:5)

从技术上讲,不需要析构函数调用。实际上比抱歉更安全(打电话给析构函数)

答案 2 :(得分:4)

除了Ben Voigt的答案之外,详细说明了省略析构函数调用的详细信息,重要的是确保内存正确对齐以便放置新类型。我会尝试将其写为requested by to OP

这一行:

S *newS = new(buffer + offset)S;

仅在地址buffer + offset正确对齐时才有效:

  

3.11对齐
   1 对象类型具有对齐要求(3.9.1,3.9.2),这些要求对地址施加限制   可以分配该类型的对象。对齐是一个   实现定义的整数值,表示字节数   在连续地址之间可以分配给定对象   [...]

对于具有基本对齐要求的任何类型,

buffer本身都已正确对齐:

  

3.7.4.1分配功能
   2 [...]
  返回的指针应该   适当对齐,以便它可以转换为任何指针   具有基本对齐要求的完整对象类型(3.11)   然后用于访问分配的存储中的对象或数组   [...]

要知道类型的对齐要求,有alignof(type)。然后有std::max_align_talignof(std::max_align_t)返回具有基本对齐要求的所有类型的最大对齐值。

有一种特殊情况需要扩展对齐的类型,以确保您的类型不是其中之一我会将其包含在您的程序中:

static_assert(alignof(S) <= alignof(std::max_align_t),  
              "Extended alignment required for S");

然后,您只需确保offsetalignof(S)的倍数。