在C++中重载new和delete

4
我在Stroustrup的书中看到了这一行代码:一个操作符函数必须是成员函数,或者至少有一个参数是用户定义类型(重新定义new和delete操作符的函数除外)。但是,operator newoperator delete不是需要至少一个用户定义类型的参数吗?这是什么意思呢?我有什么地方理解有误吗?
4个回答

5
斯特劳斯特鲁普的引言显然适用于运算符重载。C++语言仅支持用户定义类型的运算符重载。这意味着重载函数(operator )必须是用户定义类型的成员,或者是带有至少一个用户定义类型参数的独立函数。这正是该引言所指的内容。
然而,独立的(非成员)operator new和operator delete函数并不需要将用户定义类型作为它们参数之一。这可能被视为与引言相矛盾的地方。
然而,在现实中并不存在矛盾。这些运算符实际上并没有真正“重载”。当您提供自己版本的独立operator new / operator delete时,您实际上是“替换”库提供的版本。这是语言规范的官方术语:替换,而不是重载。这就是为什么上述引言实际上并不适用于operator new和operator delete的原因。

void *operator new(size_t, my_sandbox &) 肯定是一个重载。但是类成员 new/delete 的搜索路径在重载之外。 - Potatoswatter
是的,但我的意思是你可以编写自己版本的“标准”operator new:void * operator new(size_t)。这个似乎违反了引文中的要求。然而这是合法的。 - AnT stands with Russia
抱歉,my_sandbox是一个误导。void *operator new(size_t, int)也是一种重载。此外,“搜索路径”是不恰当的措辞。operator new在类作用域中查找,使用重载规则,然后落入全局作用域,在那里用户可能有一个替换默认值。我的观点是两者都会发生。顺便问一下,你看到我的回答有什么问题吗? - Potatoswatter
@Potatoswatter:好的,你说得对,void *operator new(size_t, int)确实是一个重载。我们可以安全地得出结论,operator newoperator delete是“特殊”的。事实上,我记得在SO上争论过这两个东西并不是真正的运算符,而只是使用看起来像运算符重载的语法的特殊函数。 - AnT stands with Russia
你可能引用了 §13.5/5,它基本上说除了名称以外的任何相似之处都是纯巧合。 :v) - Potatoswatter

3

如果您希望增加功能(例如日志记录或泄漏检测),则可以重载普通全局new运算符,适用于所有类,但没有办法调用旧的new定义。因此,您可能需要在重新定义的operator new中从内部调用malloc()来获取实际所需的内存。


如果这是重载(甚至覆盖),那么就有一种方法可以调用原始的全局 new。这不是重载。 - wilhelmtell
1
实际上这是替换而不是重载,因此一旦您提供了自己的 new/delete,就没有办法调用“原始”的函数。 - Andreas Brinck

2

a + b只是语法糖,实际上等同于a.operator+(b)operator+(a, b)

然而,new Foo(x, y, z)并不仅仅是语法糖,等同于operator new(Foo, x, y, z)或类似的操作。它要复杂得多:

void* address = operator new(sizeof(Foo)); // here is the behavior you can replace
try {
    new(address) Foo(x, y, z);
} catch (...) {
    operator delete(address);
}

正如你所看到的,函数operator new仅仅分配内存,这只是new操作符完成工作的一半。在我看来,将其命名为allocate_memory或类似名称会更有意义。它绝对不是像operator+那样的运算符。


0

operator newoperator delete是根据newdelete表达式中操作数的类型以及任何额外的括号参数来查找的,因此它们可以被重载(并且operator new受到重载决议的影响),但是这是通过与其他运算符不同的机制实现的。(C++03 §13.5/5)

由于它们处理原始内存,因此它们永远不会处理指向客户端类类型的指针。operator new始终采用size_t参数(可能还有其他参数,其中没有一个需要是用户定义的类型),并返回void *operator delete始终采用void *参数和可选的size_t,无论如何查找。

我能想到两个原因:

  • 它们处理原始内存,而不包含构造对象。对于operator newoperator delete尝试访问返回的内存块中的对象总是错误的。(然而,未用于构造对象的内存是可以使用的。)
  • 类成员operator newoperator delete可能会被多重和/或虚拟继承,因此无法通过任何巫术使所涉及的void*指向尚未构造或已销毁的子对象。

尽管讨论了相关的领域,但它并没有很清楚地回答实际问题。我不会投反对票,但也不会投赞成票。 - Donal Fellows
我想我没有直接切入要点,但我确实直接解释了它们在没有用户定义类型参数的情况下如何和为什么会过载。我认为这是核心问题...也许我在那里漏掉了什么。 - Potatoswatter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接