在泛型编程中使用 placement new

Using placement new in generic programming

提问人:L. F. 提问时间:8/18/2019 更新时间:11/2/2019 访问量:349

问:

在泛型代码中使用 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);
}

这篇文章从标准的角度解决了以下几点:

  • 为什么使用而不仅仅是::newnew;

  • 为什么需要显式强制转换为。void*

语言-律师 泛型-编程 放置-新 C++-常见问题

评论


答:

11赞 L. F. 8/18/2019 #1

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

为什么使用而不仅仅是::newnew

::new确保在全局范围内查找。相反,plain 首先在类的作用域中查找 if 是类类型(或其数组),然后才回退到全局作用域。根据 [expr.new]/9operator newnewT

如果 new-expression 以一元运算符开头,则 在全局范围内查找分配函数的名称。 否则,如果分配的类型是类类型或其数组, 分配函数的名称在 的作用域中查找。如果 此查找无法找到名称,或者如果分配的类型不是 类类型,则分配函数的名称在全局中查找 范围。::TT

例如,使用

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

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

由于 [new.delete.placement]/1,无法替换全局:operator new(std::size_t, void*)

这些函数是保留的;C++ 程序不能定义替换 C++ 标准库中的版本的函数 ([约束])。[basic.stc.dynamic] 的规定不适用 到这些保留的放置形式 和 .operator newoperator delete

(有关重载的更多信息,请参阅如何编写符合 ISO C++ 标准的自定义 new 和 delete 运算符?operator new

为什么需要显式强制转换为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) Tptrint*void*void*operator new


来自评论:

但是,为什么我们要完全全球地称呼一些 类型本身有特殊的重载吗?看起来很正常 超载操作员更合适 - 为什么不合适?newvoid*

通常,用于分配目的。分配是用户应该控制的东西。用户可以为普通 .newnew

然而,在这种情况下,我们不想分配任何东西——我们只想创建一个对象!放置 new 更像是一种“黑客”——它的存在很大程度上是由于缺乏可用于在指定地址构造对象的语法。我们不希望用户能够自定义任何内容。然而,语言本身并不关心这个黑客——我们必须特别对待它。当然,如果我们有类似的东西(在 C++20 中出现),我们会使用它!construct_at

另请注意,这适用于最简单的情况,即您只想在原始分配空间中复制构造一系列对象。标准容器不仅允许您自定义元素的分配方式,还允许您通过分配器自定义元素的构造方式。因此,它们通常不用于其元素 - 它们称为 .此功能由 使用。std::uninitialized_copystd::uninitialized_copystd::allocator_traits<Allocator>::constructstd::scoped_allocator_adaptor

评论

0赞 L. F. 8/24/2019
@Qwertiy 对于放置,只有全局版本是合适的 - 我们不想分配任何东西。放置的唯一目的是在指定地址构造一个对象。调用用户定义的会搞砸。newnew
0赞 Qwertiy 8/24/2019
那为什么要用它超载呢?因此,同时我们允许用户进行重载,但想在创建对象时绕过它?
0赞 L. F. 11/2/2019
@Qwertiy是的。我们只想创建一个对象;我们不打算调用除构造函数之外的任何用户定义函数。
0赞 L. F. 11/2/2019
@Qwertiy我已经更新了答案以解决您的问题。
0赞 Qwertiy 11/2/2019
是的,看到它。不错的:)