在泛型编程中使用 placement new

Using placement new in generic programming

在泛型代码中使用placement new 在指定地址构造对象时,使用模式与通常的代码有点不同。例如,考虑 uninitialized_copy 的这个实现:([uninitialized.copy])

template <class It, class For>
For uninitialized_copy(It first, It last, For dest)
{
    using T = typename std::iterator_traits<For>::value_type;
    for (; first != last; ++first, (void)++dest)
        ::new (static_cast<void*>(std::addressof(*dest))) T(*first);
}

本文post从标准的角度解决了以下几点:

(此答案使用 N4659,最终的 C++17 草案。)

为什么使用 ::new 而不是 new

::new 确保在全局范围内查找 operator new。相反,如果 T 是 class 类型(或其数组),则普通 new 首先在 class 的范围内查找,然后才返回到全球范围。每 [expr.new]/9:

If the new-expression begins with a unary ​::​ operator, the allocation function's name is looked up in the global scope. Otherwise, if the allocated type is a class type T or array thereof, the allocation function's name is looked up in the scope of T. If this lookup fails to find the name, or if the allocated type is not a class type, the allocation function's name is looked up in the global scope.

例如,与

struct C {
    void* operator new(std::size_t, void* ptr) noexcept
    {
        std::cout << "Hello placement new!\n";
        return ptr;
    }
};

普通 new 会导致找到此函数,从而打印不需要的消息,而 ::new 仍会找到全局函数并正常工作。

由于[new.delete.placement]/1:

无法替换全局operator new(std::size_t, void*)

These functions are reserved; a C++ program may not define functions that displace the versions in the C++ standard library ([constraints]). The provisions of [basic.stc.dynamic] do not apply to these reserved placement forms of operator new and operator delete.

(有关重载 operator new 的更多信息,请参阅 How should I write ISO C++ Standard conformant custom new and delete operators?。)

为什么需要显式转换为 void*

虽然全局operator new(std::size_t, void*)可能不会被替换,但可以定义新版本的::operator new。例如,假设以下声明放在全局范围内:

void* operator new(std::size_t, int* ptr) noexcept
{
    std::cout << "Hello placement new!\n";
    return ptr;
}

然后::new(ptr) T将使用这个版本而不是全球版本,其中ptr是一个int*值。指针显式转换为 void* 以确保 operator newvoid* 版本(我们打算调用)在重载决议中获胜。


来自评论:

But why do we want to call exactly global new for void* if some type has special overload of new for itself? Seems like normal overloaded operator is more suitable - why it's not?

通常,new 用于分配目的。分配是用户应该控制的事情。用户可以为正常 new.

推出更合适的版本

然而,在这种情况下,我们不想分配任何东西——我们想要做的就是创建一个对象!放置 new 更像是 "hack" — 它的存在主要是由于缺少可用于在指定地址构造对象的语法。我们不希望用户能够自定义任何东西。然而,语言本身并不关心这个 hack,尽管如此——我们必须特殊对待它。当然,如果我们有类似 construct_at 的东西(C++20 中会出现),我们将使用它!

另请注意,std::uninitialized_copy 适用于最简单的情况,即您只想在原始分配的 space 中复制构造一系列对象。标准容器允许您通过分配器自定义元素 分配 的方式,以及 构造 的方式。因此,他们通常不使用 std::uninitialized_copy 作为他们的元素——他们调用 std::allocator_traits<Allocator>::constructstd::scoped_allocator_adaptor.

使用此功能