C++中重载delete应该如何表现?

9

我遇到的问题是,据我所知,delete运算符应该是一个静态函数,但有时编译器(VC++)似乎会将其视为动态函数。

假设:

class Base
{
public:
  void* operator new(size_t size) { /* allocate from custom heap */ }
  void operator delete(void *p) { customFree(p, sizeof(Base)); }

  Base() {}
  virtual ~Base() {}
};

class Derived: public Base
{
public:
  void* operator new(size_t size) { /* allocate from custom heap */ }
  void operator delete(void *p) { customFree(p, sizeof(Derived)); }

  Derived() {}
  virtual ~Derived() {}
}

我看到的情况是删除基指针将导致调用Derived::operator delete。

Base *p = new Derived();
delete p; //calls Derived::operator delete

如果我不定义任何析构函数,那么我得到的结果正好是我所期望的:调用Base::operator delete。这似乎是因为当定义了析构函数时,编译器会向vtable中插入一个名为“scalar deleting destructor”的函数。然后该函数将调用Derived::delete。
所以我有两个问题: 1)这是标准行为吗? 2)什么情况下应该使用?
void operator delete( void *, size_t );

对比。

void operator delete( void * );

如果上述是标准行为?
2个回答

8
这是标准行为。如果使用派生类的operator new,它的operator delete也将被使用(请注意,即使您没有明确告诉编译器这些函数是静态的,它们也会被隐式声明)。可能会出现一种淘气的情况,即在派生类中有一个operator new,但相应的operator delete在基类中。我认为这是有效的,但我会避免这种情况。依赖基本的operator delete,同时在派生类中定义自己的operator new,将不可避免地引起麻烦。
如果我不定义任何析构函数,那么我会得到我所期望的结果:
你会得到未定义的行为:)一切都可能发生,包括你错误地预期的事情。通过指向另一类型对象的基指针进行删除需要虚析构函数。隐式声明的析构函数不是虚拟的。
什么时候应该使用void operator delete(void*,size_t)?
如果您想在operator delete中知道分配的大小。我在这里写了关于它的含义: C++中的new运算符除了分配和调用构造函数还做了什么?。如果您在重载的成员operator delete/new中使用全局operator new&delete来获取和释放内存,甚至是malloc/free,您不需要那个大小信息。但它可能对日志记录有用。

忽略我刚刚删除的评论。那么使用void operator delete(void*,size_t)是否比使用sizeof(classname)有任何好处? - BigSandwich
啊,不用在意。我猜new()可以分配任何大小的内存,所以sizeof(classname)可能会给你错误的大小。但在我这种情况下,它们是等价的。谢谢。 - BigSandwich
如果您不使用数组形式,则必须精确地分配sizeof(T)。因此,您的delete操作中的size_t应始终精确为sizeof(T),除非您实际上要删除派生类的内存(如果派生类没有operator delete,则会发生这种情况)。 - Johannes Schaub - litb
@JohannesSchaub-litb 这不都是关于类作用域的概念吗?我的意思是,当调用delete时,它会在派生类作用域中查找适当的版本,因为它找到了一个版本,所以就会调用那个版本。这就是你所说的“如果使用了派生类的operator new,则也将使用其operator delete”的内在含义。如果我错了,请纠正我。 - ubuntugod

6

以下是标准文件中相关的摘录:

1. delete-expression 运算符会销毁由 new-expression 创建的最底层派生类对象或数组。 delete-expression: ::opt delete cast-expression ::opt delete [ ] cast-expression 第一个选项适用于非数组对象,第二个选项适用于数组。操作数应为指针类型,或者是具有单一转换函数(class.conv.fct)到指针类型的类类型。结果具有 void 类型。

3. 在第一个选项(delete object)中,如果操作数的静态类型与其动态类型不同,则静态类型应该是操作数的动态类型的基类,并且静态类型应该有虚析构函数,否则行为未定义。在第二个选项(delete array)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。19)


“嘿,我应该先发布再查看”绝对是个好主意 :p 你有5分钟的时间纠正错误,然后才会出现“已编辑”标签 :DD - Johannes Schaub - litb

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