【问题标题】:Using placement new in generic programming在泛型编程中使用placement new
【发布时间】:2019-12-23 15:36:59
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ language-lawyer generic-programming placement-new c++-faq


    【解决方案1】:

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

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

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

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

    例如,用

    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*)

    这些功能是保留的; C++ 程序不能定义取代 C++ 标准库中的版本的函数 ([constraints])。 [basic.stc.dynamic] 的规定不适用 到operator newoperator 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 将使用此版本而不是全局版本,其中ptrint* 值。指针被显式转换为void*,以确保operator new(我们打算调用它)的void* 版本在重载决议中获胜。


    来自评论:

    但是,如果有的话,为什么我们要为 void* 准确地调用全局 new 类型本身有特殊的 new 重载吗?看起来很正常 重载运算符更合适——为什么不合适?

    通常,new 用于分配目的。分配是用户应该控制的事情。用户可以推出更适合普通new的版本。

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

    另请注意,std::uninitialized_copy 适用于您只想在原始分配空间中复制构造对象序列的最简单情况。标准容器不仅允许您自定义元素的分配方式,还允许您通过分配器自定义元素的构造方式。因此,他们通常不使用std::uninitialized_copy 作为他们的元素——他们称之为std::allocator_traits&lt;Allocator&gt;::construct。此功能由std::scoped_allocator_adaptor使用。

    【讨论】:

    • @Qwertiy 对于放置new,只有全局版本是合适的——我们不想分配任何东西。放置new 的唯一目的是在指定地址构造一个对象。调用用户定义会搞砸。
    • 那为什么 used 为它做了一个重载呢?所以同时我们允许用户进行重载,但是想在创建对象时绕过它?
    • @Qwertiy 是的。我们只想创建一个对象;除了构造函数,我们不打算调用任何用户定义的函数。
    • @Qwertiy 我已更新答案以解决您的问题。
    • 是的,看到了。不错:)
    猜你喜欢
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2017-10-12
    • 1970-01-01
    相关资源
    最近更新 更多