关于C++全局operator new重载与替换的困惑
C++全局operator new的重载与替换疑惑
一、符合预期的重载场景
先看这段代码,实现了带额外参数的全局operator new:
#include <iostream> using namespace std; void *operator new( size_t n, const string &pat ) { char *p = static_cast<char *>(::operator new( n )); const char *pattern = pat.c_str(); if( !pattern || !pattern[0] ) pattern = "\0"; // note: two null chars const char *f = pattern; for( int i = 0; i < n; ++i ) { if( !*f ) f = pattern; p[i] = *f++; } cout<<"overload!"<<endl; return p; } int main() { string fill( "<garbage>" ); string *string1 = new string( "Hello" ); // 调用标准版本 string *string2 = new (fill) string( "World!" ); // 调用重载版本,因为fill作为额外参数匹配重载的参数列表 cout<<string1<<" "<<string2<<endl; return 0; }
这段代码的行为符合普通函数重载逻辑:我们定义的operator new多了一个const string&参数,属于标准版本的重载。当使用new (fill)这种定位new语法时,编译器会匹配这个带额外参数的版本;而普通的new表达式则调用标准库的全局operator new,其签名如下:
// from new.h void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc) __attribute__((__externally_visible__));
二、超出预期的行为:同签名的"重载"
但当我们添加一个和标准签名完全一致的全局operator new(即overload2)时,情况就变了:
#include <iostream> using namespace std; // overload1:带额外参数的重载版本 void *operator new( size_t n, const string &pat ) { char *p = static_cast<char *>(::operator new( n )); const char *pattern = pat.c_str(); if( !pattern || !pattern[0] ) pattern = "\0"; // note: two null chars const char *f = pattern; for( int i = 0; i < n; ++i ) { if( !*f ) f = pattern; p[i] = *f++; } cout<<"overload1"<<endl; return p; } // overload2:和标准版本参数完全一致 void* operator new(size_t n){ cout<<"overload2"<<endl; return malloc(n); } int main() { string fill( "<garbage>" ); string *string1 = new string( "Hello" ); // 调用overload2而非标准版本 string *string2 = new (fill) string( "World!" ); // overload1内部调用的也是overload2 cout<<string1<<" "<<string2<<endl; return 0; }
运行后会发现,不仅普通new表达式调用了overload2,连overload1里显式写的::operator new(n)也没有调用标准版本,而是指向了overload2——这完全不符合普通重载的规则,因为普通重载要求参数列表不同,同签名的函数会报重定义错误,但这里却能正常编译且覆盖了标准版本。
结合<new>头文件的注释:
These are replaceable signatures:
- normal single new and delete (no arguments, throw bad_alloc on error)
- normal array new and delete (same)
- nothrow single new and delete (take a nothrow argument, return NULL on error)
- nothrow array new and delete (same)
Placement new and delete signatures (take a memory address argument, does nothing) may not be replaced by a user's program.
这里的核心疑问是:overload2到底是替换了标准版本,还是仅仅是重载?为什么它的行为和普通重载相悖,但大家仍习惯称之为"重载"?
三、本质:特殊替换而非普通重载
从C++标准的语义来讲,overload2属于替换标准库的可替换函数,而非普通重载:
- 普通重载的前提是参数列表不同,而overload2和标准版本的签名完全一致,不符合重载的定义。如果是普通函数,同签名的全局函数会触发重定义错误,但
operator new的这些特定签名是标准允许用户替换的特殊情况。 - 替换的语义是覆盖:当你在全局作用域定义了这些可替换签名的
operator new后,编译器会优先使用你的版本,甚至显式的::operator new也会绑定到你定义的版本——因为全局作用域下你的实现覆盖了标准库的版本。 - 带额外参数的版本才是真正的重载:比如overload1这种带
const string&参数的,属于普通重载,和标准版本共存,只有通过定位new语法(new (args))才会匹配调用。
至于为什么大家习惯把替换也叫"重载",其实是口语化的简化:因为自定义内存分配的场景中,替换标准签名和添加重载版本常常一起出现,久而久之就被混称为"重载全局operator new",但从标准语义上,这两者的本质完全不同。
内容的提问来源于stack exchange,提问作者Paoer111
相关产品推荐
相关产品推荐

