【问题标题】:Placement-new vs new-expressionPlacement-new 与 new-expression
【发布时间】:2021-08-22 23:19:06
【问题描述】:

再次与展示位置new 我在这个论坛上找到了一个这样的例子:

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation
  • 但我认为这里buf 是一个指向已分配和构造的默认初始化字符动态数组的指针。所以数组中的字符都是默认初始化的,并且有一个不确定的值。

  • 我猜想使用第二行中的new 位置会在之前构造的对象数组上构造对象。

  • 为什么用户没有在数组分配上调用operator new而不是使用new表达式?:

     char *buf  = static_cast<char*>(operator new[](sizeof(string)));
    
  • 毕竟我认为如果buff 是一个指向非默认可构造对象的动态数组的指针,那么代码将无法使用 new 表达式而不是使用 operator new 函数进行编译。

  • 我的猜测正确吗?

这是原始答案的链接:

What uses are there for "placement new"?

【问题讨论】:

  • @TedLyngmo:因为我认为 new 表达式完成了三个任务:分配、构造,最后返回一个指向新分配和构造的内存的指针。不是吗?
  • 典型运算符 new:void * operator new(size_t size) { void * p = malloc(size); return p; } 是的,这是默认初始化,也就是“无操作”。 New 表达式调用 operator new 作为其一部分,但它还提供您提供的构造函数调用或初始化,假设默认构造函数或默认初始化。使用动态内存,后者等于 noop。
  • @TedLyngmo "new char[sizeof(string)] 分配一个数组,最有可能 4 或 8 个字节长" - sizeof(string) 远远超过 4/8 个字节,尤其是如果它实现 SSO
  • @RemyLebeau 你很可能是正确的。我走了。该标准允许sizeof(string) 为任何字节大小。并不是说它必须超过 4 或 8。通常是。编辑:(这不是 - 真的)我不能把自己从这个坟墓里挖出来。我完全错了。我确实知道string 的样子,但我的大脑冻结了。
  • @TedLyngmo:您似乎误解了new operator vs operator new 之间的细微差别。此外,new char[sizeof(string)] 确实默认初始化。只是char 的默认初始化是noop。您可能正在考虑“值初始化”,它没有这样做。

标签: c++ new-operator placement-new new-expression


【解决方案1】:

为什么用户没有在数组分配上调用 operator new 而不是使用 new 表达式?:

我们无法回答这个问题,因为我们不是那个用户。您应该向用户询问 - 尽管该示例是在 1998 年编写的,但与他们联系可能并不容易。我的猜测:他们不知道非放置运算符 new 存在,或者他们不知道它的用途。在这种情况下,重用 char 数组的内存是一种直观的选择。

请注意,创建单个动态 std::string 对象的示例首先没有什么意义(我假设示例中的 string 就是这样)。

我有一个类似的问题要问您:为什么您在建议中使用operator new[] 而不是operator new?更重要的是,为什么不使用分配器?

我的猜测正确吗?

  1. 正确。
  2. 正确。
  3. 这是一个问题,而不是猜测。我在上面介绍过。
  4. 它会失败。但这无关紧要,因为 char 是默认可构造的。

【讨论】:

  • 这也适用于整数吗? int* p = new int[10];std::string* pstr = new(p)std::string("Hi there!");?
  • @ItachiUchiwa 整数适用于什么?
【解决方案2】:

char 是一种既基本又微不足道的对象类型。实际上,创建一个不会触及内存,而将它们组成一个数组也不会。

char* foo = new char[10];

和

char *foo  = static_cast<char*>(operator new[](10));

最终在机器代码中做同样的事情,除了第二个更冗长。

抽象机有一些细微的差别;在一个中创建了一堆字符,在另一个中,它们不在该行上。提出一个需要大量语言律师工作的案例(我认为处理可能会有所不同,并且某些访问权限可能会有所不同,尤其是在 c++ 修复 malloc 问题之前的标准版本中)。

毕竟我认为如果buff 是指向非默认可构造对象的动态数组的指针,那么代码将无法使用 new 表达式而不是使用 operator new 函数进行编译。

当然,但是演员表会是代码味道,buf 的重点是为以后的新位置存储。我猜它已经是了,

void *foo  = operator new[](10);

不那么疯狂了。

仅仅因为你可以静态转换并不意味着你应该。

【讨论】:

    【解决方案3】:

    operator new[](sizeof(string)) 这很奇怪,它创建对象的语法不正确。在最好的情况下,它在内存中隐式创建一个对象(如果 operator new 实现为 std::malloc 调用并且 object 是 POD 类型),而不初始化或构造一个。在这种情况下你所能做的就是static_cast&lt;char*&gt;(new string); 提供的行只会在动态存储中创建一个string 对象,然后通过将指针类型替换为char* 来使其匿名。

    问题是,对于放置 new buf 没有必要指向动态内存。它可以是一个静态缓冲区。它可以是一个指向用于存储多个对象的相当大的存储中的内存位置的指针,一个内存池。新对象将在给定位置构建。

    请注意,在放置新std::string 的情况下,数据存储仍然像往常一样 - 它在动态内存中分配字符数据。要使用一些内存池,程序员应该提供适当的分配器,这是放置 new 运算符的目的之一。

    【讨论】:

    • that's something odd, its incorrect syntax 为什么这么说?为什么编译时没有警告?
    • @eerorika 因为它是直接的运算符调用,而 C++ 出于实现原因允许这样做。但这不会构造或初始化创建的对象,如果直接调用 POD 类型是否与相同类型的新表达式相同,或者我至少找不到它们相等的任何引用,则它是未定义的。例如,一个新的表达式可以生成编译器实现使用的陷阱值。
    【解决方案4】:

    不,buf 不是对象数组。它是一个字符数组,所以基本上是一个字节数组。虽然它被分配了一个新的数组,但它基本上被用作一个字节指针。

    placement new 的使用是如果你想在一个确切的位置分配一个对象,但你想这样做遵循 C++ 对象分配的所有规则——所以调用构造函数并设置 vtables。通常的用例是如果您正在执行自己的自定义内存分配并重用现有内存地址。固件可以使用它来重用内存作为池。或者 RTOS 可能会使用它,以便它不会超出任务的内存限制。

    这实际上是一个糟糕的例子,说明它是如何使用的。您永远不会新建一个数组,然后将新数组放入其中。您将有一个指向周围已分配内存块的指针,并且您将在其中使用放置新的位置。

    【讨论】:

    • No, buf isn't an array of objects. It's an array of characters。这是一个矛盾。人物也是对象。
    • 不,没有区别。原始类型的对象仍然是对象。是的,默认分配器确实使用 operator new 进行分配。不,分配器不会将新放置到内存中。标准容器放置新到它们从提供的分配器获得的内存中。
    • 对象在 C++ 中具有非常特殊的含义。例如,它与您可能从面向对象编程范式的术语中熟悉的含义不同。没有适用于所有计算机科学的通用术语。
    • @gabe 如果您不同意 C++ 标准的术语定义,我建议您不要使用您个人的替代定义来回答 C++ 问题。尤其是没有澄清。
    • @GabeSechan 恰恰相反。坚持认为对象不是对象,因为它们是“原始的”,这会导致混淆。我之前放过这张幻灯片,但我现在还必须纠正你,C++ 中的“原始类型”没有意义。但我将“原始类型”解释为您指的是基本类型。
    猜你喜欢
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-25
    • 1970-01-01
    相关资源
    最近更新 更多