【问题标题】:Why is there no realloc equivalent to the new/delete family?为什么没有 realloc 等同于 new/delete 系列?
【发布时间】:2021-09-13 13:31:21
【问题描述】:

正如标题所说,我知道在 new/delete 系列运算符中没有与 C 的 realloc 等效。

我已经找到了this 的问题,该问题略微涉及该主题,但并没有真正回答“为什么”。

我的问题是:

  1. 为什么能够重新分配对象是个坏主意?
  2. 为什么改变对象的大小是个坏主意? (实现集合似乎是对象更改其大小的完全正当理由。)
  3. 在这些情况下会违反什么规则,为什么这条规则客观上是好的?

【问题讨论】:

  • 如果你需要重新分配一个“数组”,你可能会寻找std::vector,所以不需要明确的realloc
  • 最接近的等价物是使用placement new
  • 猜猜? C++ 是关于确定性构造和破坏的。如果确实存在像realloc 这样的表达式......它就不是确定性的。它可能只是创建新对象……或者它可能会破坏一些现有的对象……或者它可能会破坏整个现有缓冲区并重新初始化一个新对象。那是……嗯。使用库组件比使用核心语言部分(恕我直言)更好。
  • realloc for new[] 可能是合理的,但这不会改变对象的大小,只会改变分配对象的数量。但这已经被std::vector 覆盖了。 realloc for new 完全没有意义。对象的大小是编译时常量。 PS:我不认为placement new 是等价的,因为它没有重新分配。它只是在已分配的内存中调用构造函数。
  • @StoryTeller-UnslanderMonica “C++ 是关于确定性的……” 在 UB 中紧张地吹口哨

标签: c++ memory memory-management


【解决方案1】:

Realloc 有两种行为,其中一种在 C++ 对象模型中是不可接受的。 Realloc 可以增加一块存储的大小,也可以分配新的存储并将旧存储中的所有内容复制到新存储中。

问题是,C++ 并不认为对象只是比特袋。它们是具有不变量的活生生的呼吸类型。而且其中一些不变量不能容忍它们的位被很好地复制。

在 C++ 中,复制对象的位并不意味着您已经有效地复制了该对象。这仅适用于可简单复制的类型,并且有很多类型不可简单复制。

因此,C++ realloc 等价物不能用于任何分配。您需要将调用拆分为两个单独的调用:一个尝试扩展内存,如果不能,什么都不做,另一个是常规的堆分配调用,您可以使用现有的 C++ 手动复制到其中技术。


例如,许多std::list 实现在std::list 对象本身中存储了一个终止节点,用于表示链表的开始/结束。如果你只是简单地复制它的位,指向终止节点的指针将指向现在已经消失的旧分配。

这很糟糕。

为了允许对象具有访问这些类型的代码可以维护的任意类不变量,有必要将对象视为不仅仅是其对象表示的位。并且大多数 C++ 类型都保留了一些不变量,其对象表示无法在按位复制中存活。

【讨论】:

  • @RaresDima -class A { int i; int *pi = &i; ... }; - 你不能只是按位传输它。现在考虑这就是 SSO 的基础,您遇到了一个大问题。
  • @RaresDima std::string 就是一个很好的例子。
  • @0x5453 - 小字符串优化
  • 将此类对象移动到新位置会使其状态无效。对我来说 - 这意味着它不是简单可复制的。
  • @Tomek 如果 StoryTeller 的 A... 中没有特殊成员,则它是可简单复制的。在那种情况下,这是一个糟糕的课程
【解决方案2】:

您不能更改 C++ 中现有对象的存储。您唯一能做的就是在“重新分配”的内存中创建新对象,该对象将具有与原始对象相同的内容。这正是std::vector 的能力。

C++ 的一个问题是这个功能通常不仅仅是复制字节。通过复制对象的二进制表示来复制对象的内容仅适用于有限的类型集——所谓的 trivially-copyalbe 类型。对于其他的,复制/移动构造函数析构函数需要参与。

【讨论】:

  • 哪些对象不能轻易复制?我正在尝试,但我想不出一个例子
  • @RaresDima 例如,具有自定义析构函数的所有类的对象。详情请见en.cppreference.com/w/cpp/named_req/TriviallyCopyable
  • 为什么在你逐位复制对象后,相同的析构函数不起作用?
  • @RaresDima -- 试试std::string
  • @RaresDima 考虑std::unique_ptr。如果你按位复制它,现在有两个对象将在同一个指针值上调用delete
【解决方案3】:

没有这样的功能是因为 C 库没有提供合适的接口来实现。 newdelete 可以根据 mallocfree 实现,但假设的 renew 不能根据 realloc 实现,因为您不能移动 C++ 对象的字节(其他人已经指出并解释了这个事实)。当 C++ 处于早期阶段时,这是一个重要的考虑因素,并且在某种程度上仍然如此。您通常不想编写自己的,可能是劣等的分配器,因为您可以在非常成熟且经过验证的malloc 和朋友的基础上捎带。

可以根据提供类似try_realloc 的功能的低级分配器来实现renew。检查块是否可以原地增长,如果是,则增长它,分配一个新块并将现有对象移动到它。双赢?显然,事实证明这个功能并不太重要。 std::vector 将分配的大小加倍的事实上的策略工作得很好并且提供了非常好的性能,那么为什么要麻烦呢?

【讨论】:

  • 我对您对std::vector 的最后评论持怀疑态度,因为“非常好的性能”在很大程度上取决于数据和用例。具有一系列push_backs 大对象的未知大小范围在就地扩展能力方面不会有很好的性能。有很多关于这个主题的演讲和文章在使用就地扩展时显示出更好的增长特征。也不是所有的std::vector 实现都增长了 2 倍——Dinkumware 使用(或使用?)1.5,所以这不是跨平台数据的可靠/便携期望。
  • @Human-Compiler 一切都取决于数据和用例。 std::vector 在没有 realloc 的情况下存活了 20 多年,这可能意味着它对大多数用户来说已经足够好了。如果结果还是不够好,我会第一个欢迎我们的新霸主。
猜你喜欢
  • 2016-06-26
  • 2010-10-14
  • 2019-04-16
  • 1970-01-01
  • 2014-12-12
  • 2020-05-05
  • 2018-09-03
  • 1970-01-01
  • 2021-07-17
相关资源
最近更新 更多