(此答案使用 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 new 和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* 版本在重载决议中获胜。
来自评论:
但是,如果有的话,为什么我们要为 void* 准确地调用全局 new
类型本身有特殊的 new 重载吗?看起来很正常
重载运算符更合适——为什么不合适?
通常,new 用于分配目的。分配是用户应该控制的事情。用户可以推出更适合普通new的版本。
然而,在这种情况下,我们不想分配任何东西——我们要做的就是创建一个对象!放置 new 更像是一种“黑客”——它的存在很大程度上是由于缺乏可用于在指定地址构造对象的语法。我们不希望用户能够自定义任何东西。然而,语言本身并不关心这种黑客攻击——我们必须特别对待它。当然,如果我们有类似construct_at(在 C++20 中出现)之类的东西,我们会使用它!
另请注意,std::uninitialized_copy 适用于您只想在原始分配空间中复制构造对象序列的最简单情况。标准容器不仅允许您自定义元素的分配方式,还允许您通过分配器自定义元素的构造方式。因此,他们通常不使用std::uninitialized_copy 作为他们的元素——他们称之为std::allocator_traits<Allocator>::construct。此功能由std::scoped_allocator_adaptor使用。