9

在他的新书TC++PL4 中, Stroustrup对用户控制的内存分配和放置(或者更具体地说,关于神秘的“放置”)的常见做法 进行了略微不同的阐述。在书的教派。11.2.4,Stroustrup 写道:newdelete

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

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

很公平。但是,没有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()将大小作为其第一个参数,并且分配的对象的大小是隐式提供的。operator new()运算符使用的由new通常的参数匹配规则选择;每个operator new()都有 asize_t作为它的第一个参数。

“放置”operator new()是最简单的这种分配器。它在标准头文件中定义<new>

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 然后继续讨论在 arena 中放置位置的new使用。他似乎没有再提到安置delete

4

3 回答 3

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。当您查看<new>标题时,您会看到此函数为空。我会说它只是为了完整起见。即使使用模板,您也可以手动调用析构函数,您知道吗?

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 的开销时,您可以使用它。恕我直言,这是放置新/删除的唯一用例。

于 2013-06-30T16:27:30.753 回答
2

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

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

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

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

不存在更优雅的语法吗?

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

于 2013-07-03T10:59:45.837 回答
2

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

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

但是,您也可以为类重载operator newand operator delete(是的,同样,您可以使用数组变体,但仍然不应该使用它们)。

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

与往常一样,您需要确保您new的 s 和deletes 匹配,因此您应该只::operator delete在/如果您用于::operator new分配内存时使用它来删除内存。大多数时候你不应该::在任何一个上使用。

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

于 2013-06-30T17:04:37.033 回答