在泛型编程中使用 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从标准的角度解决了以下几点:
为什么使用 ::new
而不是 new
;
为什么需要显式转换为 void*
。
(此答案使用 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
仍会找到全局函数并正常工作。
无法替换全局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 new
的 void*
版本(我们打算调用)在重载决议中获胜。
来自评论:
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>::construct
。 std::scoped_allocator_adaptor
.
使用此功能
在泛型代码中使用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从标准的角度解决了以下几点:
为什么使用
::new
而不是new
;为什么需要显式转换为
void*
。
(此答案使用 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 typeT
or array thereof, the allocation function's name is looked up in the scope ofT
. 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
仍会找到全局函数并正常工作。
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
andoperator 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 new
的 void*
版本(我们打算调用)在重载决议中获胜。
来自评论:
But why do we want to call exactly global
new
forvoid*
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>::construct
。 std::scoped_allocator_adaptor
.