【问题标题】:How could I sensibly overload placement operator new?我怎样才能明智地重载放置运算符 new?
【发布时间】:2011-04-10 03:46:49
【问题描述】:

C++ 允许重载 operator new - 全局和每个类 - 通常 operator newoperator new[]new[] 语句和放置 operator new 一起使用。

这三个中的前两个通常因使用自定义分配器和添加跟踪而被重载。但是放置operator new 似乎很简单——它实际上在内部什么都不做。例如,在 Visual C++ 中,默认实现只返回传递给调用的地址:

//from new.h
inline void* operator new( size_t, void* where )
{
   return where;
}

它还能做什么?为什么以及如何合理地超载展示位置operator new

【问题讨论】:

  • 您是否要求使用重载 new 运算符?这是一个相当广泛的问题。
  • @Alexander Rafferty 我专门询问重载放置新的用途。在这种特定情况下,我看不出任何目的。
  • +1 用于进入头文件。
  • +1,有趣的问题,这就是我喜欢这样的原因:)
  • 学究式:你不重载 operator new,你替换它(或覆盖它)。重载意味着创建一个新的方法/函数,使用与现有方法相同的名称但具有不同的签名。替换函数时,使用相同的签名。

标签: c++ visual-c++ memory-management operator-overloading


【解决方案1】:

正确答案是你不能替换操作符放置新

§18.4.​1.3 安置表格
这些函数是保留的,C++ 程序可能不会定义取代标准 C++ 库中的版本的函数。

基本原理:分配和解除分配操作符的唯一目的是分配和解除分配内存,因此当给定内存时不应该再做任何事情。 (该标准特别指出这些功能“故意不执行其他操作。”)

【讨论】:

  • 哇。 Visual C++ 9 很高兴地允许这样做。
  • @sharptooth:你的测试程序是什么?毫不奇怪,它是非法的,就像在 std 命名空间中添加东西(通常)是非法的一样。语言本身并不聪明,但标准库禁止它。
  • 我通过进入编译器的头文件替换了 void* operator new[](size_t, void* where)。它工作得很好。实际上,我并没有替换它——我删除了它,使它无法使用,因为它无法使用的。
  • @PaulDuBois:嗯?编辑编译器文件是个坏主意,现在你只能在一个非常特殊的配置上构建;为了什么利益?
  • 没用,您仍然可以覆盖特定于类的放置新操作。
【解决方案2】:

从技术上讲,operator new 是任何operator new,除了所需的内存大小之外,它还接受其他参数。

所以,new(std::nothrow) X 使用了一个展示位置 operator newnew(__FILE__, __LINE__) X 也是如此。

覆盖operator new(size_t, void*) 的唯一原因可能是添加跟踪信息,但我认为对此的需求将非常低。

【讨论】:

  • 实现它的一个原因可能是强制使用特定的内存分配器;当我们在 Windows 上做复杂的事情时,我们会遇到这种情况,因为必须强制使用一个特定 DLL 而不是另一个 DLL 使用的分配器。 (是的,不同的库使用不同的分配器。只要代码匹配来自同一个库的new 和来自同一个库的delete。)
  • 第一句错了,placement new有特定含义。并且放置 new 不能超载或替换。
  • @GMan:你是对的。接受额外参数的operator new 重载似乎没有特定名称,尽管它们通常使用放置语法来调用。
  • @GMan: new 使用额外参数调用称为“新位置”,无论该额外数据是否是应构造对象的地址。来自标准“new-placement 语法用于为分配函数提供附加参数。” (部分[expr.new])。答案的前两句是正确的。最后一个是错误的,替换::operator new(size_t, void*)是被禁止的,所以你不能添加跟踪。
【解决方案3】:

One example 在 Stroustrup 的常见问题解答中。

【讨论】:

  • 老实说,我不明白那个例子。
  • @sharptooth 找到了一个类似的例子,有一些额外的解释here(虽然有一些轻微的不准确)
【解决方案4】:

最明显的覆盖是复制这个实现。

另一个明智的做法是添加一些检查(例如,验证请求区域内没有“绑定标记”)。

但我认为,由于名称查找的机制(或不覆盖它以防止其使用),一旦您覆盖其他(对于给定的类),这一点不仅仅是您必须覆盖它,这也很好,但这是一个有意识的决定)。

【讨论】:

    【解决方案5】:

    为保留区域定义自己的内存管理是一种很好的用途。

    对相同的物理数据有不同的视图(无需移动数据)是其他有趣的用途。 它还允许您将结构化文件读取为缓冲区上的字符,然后通过在缓冲区上定义该类的对象来叠加它们的逻辑结构。 这个东西与文件的内存映射相结合,可以大大提高性能。 内存映射硬件... 所以,成千上万的应用程序!

    【讨论】:

    • 您能举个例子吗?这个问题不是关于在用户代码中使用placement new,而是关于重载运算符。
    【解决方案6】:

    我见过一个例子,其中两个参数 new [] 被覆盖以返回预先填充了作为附加参数传递的 char 的内存块。我不记得原始代码使用了什么(可能是 memset()),但它在功能上是这样的:

    #include <iostream>
    #include <algorithm>
    #include <new>
    void* operator new [](size_t n, char c)
    {
            char* p = new char[n];
            std::fill(p, p+n, c);
            return p;
    }
    int main()
    {
            char* p = new('a') char[10];
            std::cout << p[0] << p[1] << ".." << p[9] << '\n';
    }
    

    虽然我猜这不会被称为“放置”新的,因为它不执行放置。如果模板化它可能会很有用,以便它可以构建任何类型的数组,填充作为其第二个参数传递的对象的副本......但是无论如何,我们都有容器。

    【讨论】:

    • 称为placement new,例如标准说:“这个开销可以应用于所有数组new-expressions,包括那些引用库函数operator new[](std::size_t, void*)和其他放置分配函数。
    【解决方案7】:

    我不太确定这个问题,但以下内容会覆盖班级级别的新安置:

    struct Bar {
    void* operator new(size_t /* ignored */, void* where) throw() { return where; }
    };
    
    int main() {
      char mem[1];
      Bar* bar = new(mem) Bar;
    }
    

    我相信这是合法的 C++(使用 gcc 4.4.6 可以正常编译和运行)。

    您可以随意更改此运算符的实现(包括删除throw() 子句,这意味着编译器在调用构造函数之前不再检查where 指针是否为空)。不过要小心行事。

    §18.4.​1.3 很有趣。我相信这仅适用于全局 operator new 函数,而不是特定于类的函数。

    【讨论】:

    • 问题是是否有任何其他实现是可能的。除了return where;,我还能做什么?
    • 啊,我明白了。好吧,您可以进行一些检查以确保该地址尚未使用,不与已分配的内存重叠,或者您甚至可以调整where 以更好地对齐(返回where + n 字节)。这些都是我在实践中从未见过的。
    • 它可以编译,但你确定你的覆盖被调用了吗?
    • 是的,我敢肯定(因为我之前不得不在生产代码中使用它)。为了验证这一点,在函数体中放入一个 throw,然后创建一个这种类型的新实例。
    【解决方案8】:

    放置新重载的最重要的额外功能是检查地址对齐。

    例如,假设某些类需要 16 字节对齐。开发人员重载 new、new[]、delete 和 delete[] - 只是为了确保所有内容都正确对齐。

    当他尝试将他的类与使用放置新的库一起使用时,一切正常......库不知道该类是否需要/什么对齐,并且地址它试图“放置”对象没有对齐 - 大繁荣。

    这种情况的最简单示例 - 尝试使用 std::vector 其中 T 需要非标准对齐。

    放置 new 的重载允许检测指针是否未对齐 - 可能会节省数小时的调试时间。

    【讨论】:

      【解决方案9】:

      我的主要用途是创建大量对象。它的性能要好得多,并且在整个块中分配内存的开销更少,即使用 Win32 中的 VirtualAlloc(在编程窗口时)。然后,您只需将该块中的一个 ptr 传递给每个新放置的对象,例如:

      char *cp = new char[totalSize];
      
      for(i = 0; i < count; i++, cp += ObjSize)        
      {                                                        
          myClass *obj = new(cp) myClass;             
      }
      

      【讨论】:

      • IIUC,他问的不是placement new算子本身的用例,而是重载它的用例。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-28
      • 1970-01-01
      • 2017-09-27
      • 2013-06-30
      相关资源
      最近更新 更多