默认放置new
操作符在 18.6 [support.dynamic] ¶1 中声明,带有非抛出异常规范:
void* operator new (std::size_t size, void* ptr) noexcept;
这个函数什么都不return ptr;
做,所以它是合理的noexcept
,但是根据 5.3.4 [expr.new] ¶15 这意味着编译器必须在调用对象的构造函数之前检查它是否返回 null:
-15-
[注意:除非使用非抛出异常规范(15.4)声明分配函数,否则通过抛出异常表示分配存储失败std::bad_alloc
(第15条,第18.6.2.1条);否则它返回一个非空指针。如果分配函数声明为不抛出异常规范,则返回 null 以指示分配存储失败,否则返回非空指针。——尾注]如果分配函数返回null,则不进行初始化,不调用deallocation函数,new-expression的值为null。
在我看来(特别是针对放置new
,而不是一般而言)这个空检查是一个不幸的性能损失,尽管很小。
我一直在调试一些代码,其中new
在对性能非常敏感的代码路径中使用放置以改进编译器的代码生成,并且在程序集中观察到 null 检查。new
通过提供使用抛出异常规范(即使它不可能抛出)声明的特定于类的放置重载,条件分支被删除,这也允许编译器为周围的内联函数生成更小的代码。说放置new
函数可以抛出,即使它不能抛出,结果是明显更好的代码。
所以我一直想知道放置new
案例是否真的需要空检查。它可以返回 null 的唯一方法是如果你将它传递给 null。尽管有可能并且显然是合法的,但可以这样写:
void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );
我不明白为什么这会有用,我建议如果程序员在使用放置之前必须明确检查 null 会更好,new
例如
Obj* obj = ptr ? new (ptr) Obj() : nullptr;
有没有人需要放置new
来正确处理空指针的情况?(即没有添加ptr
一个有效内存位置的显式检查。)
我想知道禁止将空指针传递给默认放置new
函数是否合理,如果不是,是否有更好的方法来避免不必要的分支,而不是试图告诉编译器该值不为空,例如
void* ptr = getAddress();
(void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();
或者:
void* ptr = getAddress();
if (!ptr)
__builtin_unreachable(); // same, but not portable
Obj* obj = new (ptr) Obj();
注意这个问题是故意标记为微优化,我并不是建议您new
为所有类型重载放置以“提高”性能。这种效果在一个非常具体的性能关键案例中被注意到,并且基于分析和测量。
更新: DR 1748使使用带有新位置的空指针成为未定义行为,因此不再需要编译器进行检查。