【问题标题】:Is it OK not to call the destructor on placement new allocated objects?在放置新分配的对象时不调用析构函数可以吗?
【发布时间】:2020-03-28 22:56:01
【问题描述】:

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

char *buffer; 

使用placement new在该缓冲区中分配我的结构

struct S
{ 
    std::tuple<int, double, char> m_data; 
    auto getRecord() 
    { 
        return m_data;
    }
};

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

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

【问题讨论】:

  • 如果你不需要做任何资源管理,为什么它首先要有析构函数?
  • @Barmar 编译器会自动生成一个
  • 注意buffer + offset 地址对齐。
  • @alain 请详细说明。你的意思是如果我使用缓冲区有两种不同的类型,我必须确保某事?还是对单一类型也有警告?有什么有用的链接吗?
  • @Lorah:您必须确保指针 buffer + offset 与您的类型正确对齐。如果 buffer 本身没有很好地对齐,或者如果您混合类型导致 offset 中断对齐,这可能是一个问题。

标签: c++


【解决方案1】:

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

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

许多专家一致认为“取决于析构函数产生的副作用”过于模糊而无用。许多人将其解释为重言式,意思是“如果程序在未评估析构函数的副作用时具有未定义的行为,那么未能调用析构函数会导致未定义的行为”。见Observable behavior and undefined behavior -- What happens if I don't call a destructor?

如果您的类型有一个平凡的析构函数(在您的示例中似乎就是这种情况),那么调用它(或未能调用它)没有任何效果——调用一个平凡的析构函数甚至不会结束对象。

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

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

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

【讨论】:

  • 我猜tuple 有一个微不足道的析构函数当且仅当所有元组类型都这样做时
【解决方案2】:

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

【讨论】:

  • 正是...在维护期间的某个时刻,有人可能会在S 中添加一个析构函数...除非他有严重的性能问题并且分析告诉他,否则没有人应该编写这种代码内存管理是原因,并且可以进行重大改进。
【解决方案3】:

除了 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) 的倍数。

【讨论】:

    猜你喜欢
    • 2011-05-24
    • 2018-08-14
    • 2018-10-25
    • 1970-01-01
    • 2014-09-07
    • 2020-08-14
    • 2017-06-11
    • 2012-04-29
    相关资源
    最近更新 更多