在18.6 [support.dynamic] ¶1中,默认的 new
运算符被声明为一个非抛出异常规定:
void* operator new (std::size_t size, void* ptr) noexcept;
该函数除了return ptr;
以外什么也不做,因此它被声明为noexcept
是合理的。然而,根据5.3.4 [expr.new] ¶15的说明,这意味着编译器在调用对象的构造函数之前必须检查它不会返回空指针:
-15-
[注: 除非分配函数使用了非抛出异常规格说明 (15.4),否则通过抛出std::bad_alloc
异常(第15、18.6.2.1节)来表示无法分配存储空间;否则返回非空指针。如果分配函数使用了非抛出异常规格说明,则返回null表示无法分配存储空间,并返回非空指针。 —end note] 如果分配函数返回null,那么不应进行初始化,不应调用释放函数,并且new表达式的值应为null。
对我来说(特别是对于定位new,而不是一般情况),这个null检查会对性能产生不必要的影响,尽管很小。
我一直在调试一些代码,在其中非常敏感的性能代码路径上使用了定位new
,以改进编译器的代码生成并在汇编中观察到了null检查。通过提供一个类特定的定位new
重载,即使它不可能抛出异常也声明了一个带有抛出异常规格说明的函数,这样就可以去除条件分支,从而允许编译器为周围的内联函数生成更小的代码。结果是说定位new
函数可能会抛出异常,尽管它不会,但生成的代码效果明显更好。
因此,我一直在想定位new
是否真的需要进行null检查。它只能返回null,如果你传递了null指针。虽然这是可能的,并且显然是合法的:
void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );
我看不出为什么这会有用,我建议程序员在使用放置new
之前明确检查null,例如。
Obj* obj = ptr ? new (ptr) Obj() : nullptr;
有人曾经需要使用放置new以正确处理空指针情况吗?(即,不添加明确检查ptr
是否是有效的内存位置。)
我想知道是否合理禁止将空指针传递给默认的放置new
函数,如果不行,是否有其他更好的方法可以避免不必要的分支,而不是试图告诉编译器该值不为null,例如。
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();
N.B. 这个问题特意标记为微观优化,我并不建议你在所有类型上过度使用重载放置new
来"提高"性能。这种效果是在非常特定的性能关键情况下注意到的,基于分析和测量。
更新: DR 1748使得使用空指针进行放置new操作成为未定义行为,所以编译器不再需要执行该检查。
abort()
如果无法获取内存,但它在不同的翻译单元中定义,并且我没有使用LTO。编译器如何知道我所知道的一切?它是由NSA编写的吗? - Jonathan Wakely