【问题标题】:Placement forms of the operator delete functions运算符删除功能的放置形式
【发布时间】:2013-06-30 15:58:07
【问题描述】:

在他的新书中TC++PL4, Stroustrup 对a once usual practice regarding user-controlled memory allocation and placement new 的看法略有不同——或者更具体地说,是关于神秘的“位置delete”。在书的教派。 11.2.4,Stroustrup 写道:

“placement delete”操作符除了可能通知垃圾收集器删除的指针不再安全派生之外什么都不做。

这意味着良好的编程实践将遵循a call to placement delete 对析构函数的显式调用。

很公平。但是,没有比晦涩难懂的方式来调用placement delete 更好的语法了

::operator delete(p);

我问的原因是Stroustrup的教派。 11.2.4 没有提到这种奇怪的语法。事实上,Stroustrup 并没有详述这个问题。他根本没有提到语法。我隐约不喜欢::operator 的外观,它将命名空间解析的问题插入到与命名空间无关的东西中。不存在更优雅的语法吗?

作为参考,以下是 Stroustrup 在更全面的背景下的引述:

默认情况下,操作员new 在免费存储中创建其对象。什么 如果我们想要在别处分配对象?...我们可以放置对象 通过提供带有额外参数的分配器函数和 然后在使用new时提供这样的额外参数:

void* operator new(size_t, void* p) { return p; }

void buf = reinterpret_cast<void*>(0xF00F);
X* p2 = new(buf) X;

由于这种用法,new(buf) X 语法用于提供额外的 operator new() 的参数称为 放置语法。 请注意,每个operator new() 都将大小作为其第一个参数 并且分配的对象的大小是隐式提供的。 new 运算符使用的 operator new() 由 通常的参数匹配规则;每个operator new() 都有 size_t 作为它的第一个参数。

“放置”operator new() 是最简单的此类分配器。它 定义在标准头&lt;new&gt;:

void* operator new (size_t, void* p) noexcept;
void* operator new[](size_t, void* p) noexcept;

void* operator delete (void* p, void*) noexcept; // if (p) make *p invalid
void* operator delete[](void* p, void*) noexcept;

“放置delete”操作员除了可能通知一个 已删除指针不再安全的垃圾收集器 派生的。

然后,Stroustrup 继续讨论将放置new 与竞技场一起使用。他似乎没有再次提及安置delete

【问题讨论】:

  • 你能给出更完整的报价吗?您可能会将碰巧被称为“运算符”的 函数 与实际的 运算符 混淆。 C++ 中没有放置删除表达式
  • C 中只有一种delete expression,它不允许任何放置语法(只需查看标准中的语法产生)。这就是许多相当熟练的 C++ 程序员在说“C++ 中没有 placement delete expression”时的意思。也许在 C++14 或 C++17 中会有这种效果,但现在什么都没有。
  • @KerrekSB:我现在给出了更完整的报价。
  • 有趣的是placement new 操作符也不做任何事情(嗯,它返回它的第二个参数)。所以我不知道他为什么特别呼吁删除这个位置。可能更有趣的是,new 表达式语法支持调用placement new,而delete 表达式语法不支持调用placement delete(如问题中所述)。 Placement delete 只能被显式调用,或者当作为placement new 操作的结果运行的构造函数抛出异常时被隐式调用。
  • 也许允许某种形式的直接调用构造函数(提供内存地址)会减少混乱。因为这就是放置 new/delete 的真正意义——调用构造函数和析构函数。没有任何东西被分配或释放。

标签: c++ placement-new


【解决方案1】:

如果您不想使用::,您实际上不必这样做。事实上,您通常不应该(不想)。

您可以为 ::operator new::operator delete 提供替换(以及数组变体,尽管您永远不应该使用它们)。

不过,您也可以为一个类重载 operator newoperator delete(是的,同样,您可以使用数组变体,但永远不应该使用它们)。

使用void *x = ::operator new(some_size); 之类的东西会强制分配直接转到全局operator new,而不是使用特定于类的分配(如果存在)。一般来说,当然,如果它存在,你想使用特定于类的类(如果不存在,则使用全局类)。这正是您使用 void *x = operator new(some_size); 所得到的(即,没有范围解析运算符)。

与往常一样,您需要确保您的news 和deletes 匹配,因此当/如果您使用::operator new 分配内存时,您应该只使用::operator delete 删除内存。大多数情况下,您不应该在其中任何一个上使用 ::

主要的例外是当/如果您实际上正在为某个课程编写operator newoperator delete。这些通常会调用::operator new 来获取一大块内存,然后将其分成对象大小的部分。为了分配那一大块内存,它通常(总是?)必须明确指定::operator new,否则它最终会调用自己来分配它。显然,如果它在分配数据时指定::operator new,也需要指定::operator delete来匹配。

【讨论】:

    【解决方案2】:

    首先:不,没有。

    但是内存的类型是什么?准确地说,它没有。那么为什么不只使用以下内容:

    typedef unsigned char byte;
    
    byte *buffer = new byte[SIZE];
    
    Object *obj1 = new (buffer) Object;
    Object *obj2 = new (buffer + sizeof(Object)) Object;
    ...
    obj1->~Object();
    obj2->~Object();
    
    delete[] buffer;
    

    这样您就完全不用担心展示位置的删除了。只需将整个内容封装在一个名为 Buffer 的类中即可。

    编辑

    我考虑了您的问题并尝试了很多方法,但我没有找到您所谓的placement delete 的机会。当您查看&lt;new&gt; 标头时,您会看到此函数为空。我会说它只是为了完整起见。即使使用模板,您也可以手动调用析构函数,您知道吗?

    class Buffer
    {
        private:
            size_t size, pos;
            byte *memory;
    
        public:
            Buffer(size_t size) : size(size), pos(0), memory(new byte[size]) {}
    
            ~Buffer()
            {
                delete[] memory;
            }
    
            template<class T>
            T* create()
            {
                if(pos + sizeof(T) > size) return NULL;
                T *obj = new (memory + pos) T;
                pos += sizeof(T);
                return obj;
            }
    
            template<class T>
            void destroy(T *obj)
            {
                if(obj) obj->~T(); //no need for placement delete here
            }
    };
    
    
    int main()
    {
        Buffer buffer(1024 * 1024);
    
        HeavyA *aObj = buffer.create<HeavyA>();
        HeavyB *bObj = buffer.create<HeavyB>();
    
        if(aObj && bObj)
        {
            ...
        }
    
        buffer.destroy(aObj);
        buffer.destroy(bObj);
    }
    

    这个类只是一个竞技场(Stroustrup 称之为)。当您必须分配许多对象并且不希望每次都调用 new 的开销时,您可以使用它。恕我直言,这是放置新/删除的唯一用例。

    【讨论】:

    • 你是对的。这或多或少是我一直在做的。然而,我今天想了解的是 Stroustrup 的意思,他似乎建议你我都开始做不同的事情。
    【解决方案3】:

    这意味着良好的编程实践将遵循通过调用放置删除对析构函数的显式调用。

    不,它没有。 IIUC Stroustrup 并不意味着放置delete必要通知垃圾收集器内存不再使用,他的意思是它除此之外不做任何事情。所有的释放函数都可以告诉垃圾收集器内存不再使用,但是当使用placement new 自己管理内存时,为什么还要让垃圾收集器摆弄那块内存呢?

    我隐约不喜欢::operator 的外观,它将命名空间解析的问题插入到与命名空间无关的东西中。

    “正确地”它确实与命名空间有关,将其限定为引用“全局operator new”,从而将其与类类型的任何重载operator new 区分开来。

    不存在更优雅的语法吗?

    您可能永远不想调用它。如果您使用放置new 并且构造函数抛出异常,则编译器将调用放置delete 运算符。由于没有要释放的内存(因为起搏new 没有分配任何内存),因此它可能会将内存标记为未使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-22
      • 2011-10-07
      • 2016-05-25
      • 2012-12-23
      • 2021-10-29
      • 1970-01-01
      • 2017-03-29
      相关资源
      最近更新 更多