如果操作符delete没有实现,为什么不编译它

why does this not compile if operator delete has no implementation

本文关键字:为什么不 编译 实现 操作符 delete 如果      更新时间:2023-10-16

如果操作符delete没有实现,为什么不编译?


class A
{ 
public: 
    virtual ~A(){ } 
private: 
    void operator delete(void *p); 
};
int main() 
{
    A a;
}

使用g++编译,得到以下错误:

对"A::operator delete(void*)"的未定义引用

如果给它一个空的操作员删除的实现

class A
{ 
public: 
    virtual ~A(){ } 
private: 
    void operator delete(void *p) {}
};
int main() 
{
    A a;
}

或删除"虚拟"

class A
{ 
public: 
    ~A(){ } 
private: 
    void operator delete(void *p); 
};
int main() 
{
    A a;
}

两者都编译成功。

如何解释?

好吧,之间有很大的区别

void operator delete(void *p); 

void operator delete(void *p) {}

前者只是声明函数;后者定义了它。当你声明一个函数时,你基本上只是说"有一个函数,这里是它的参数类型和返回值",但当你定义一个函数的时候,你写下了实际包含该函数的代码。用一对空大括号定义的函数什么也不做;一个已声明但未定义的函数可能会做任何事情(只有看到定义后才能知道)。显然,除非已经定义了函数,否则不能调用它。不太明显的是,除非已经定义了函数,否则函数的地址不能被占用。显然,这两种语句都意味着,如果您调用一个函数或获取函数的地址,而您未能定义它,那么您的程序就是格式错误的。

通常,未能定义需要定义的内容的结果是链接器错误。这是因为当你以需要定义的方式使用某个东西时,编译器会创建一个符号,告诉链接器在找到定义后将地址插入所需的位置。如果链接器找不到定义(因此也找不到地址),那么它就无法完成它的工作。

C++标准有一组神秘的规则,基本上告诉你在什么条件下需要定义函数。有时是,有时不是。一个微不足道的例子:

int f();
int main() {}

这很好;函数f从不被隐式或显式地调用;其地址从未被占用;它甚至没有被调用的机会。因此,即使未定义f,该程序也将进行编译。

根据标准,如果函数odr使用,则必须定义该函数。关于operator delete([basic.def.odr]/3),该标准有以下内容:

的非放置分配或解除放置函数一个类是odr,用于定义该类的构造函数。的非放置解除分配函数类的析构函数的定义使用odr,或者通过在虚拟析构函数的定义要点(12.4)。(脚注26)。。。

因此:如果您的类定义了构造函数或析构函数,它将使用您的operator delete。因此,你的析构函数是否是虚拟的并不重要;由于您定义了析构函数,因此如果没有operator delete的定义,您的程序将是格式错误的。[1]

现在,您可能会问,当析构函数是非虚拟的时,为什么编译器和链接器不抱怨。好吧,工具链有时会接受代码,即使它们应该拒绝它。在这种特殊的情况下,如果析构函数不是虚拟的,那么你的程序实际上不需要operator delete的地址,所以如果你从不定义它,链接器不会抱怨。但当你使析构函数成为虚拟的时,由于解除分配函数的查找基于要解除分配的对象的动态类型,因此需要为解除分配函数提供vtable条目,这意味着必须知道其地址。因此出现了链接错误。(我想再次重申,即使当析构函数是非虚拟的时,编译器和链接器接受代码,但根据我对标准的阅读,它仍然是格式错误的代码。)

[1] 请注意,只要您实际创建了A类型的对象,就不可能没有构造函数和析构函数的定义,即使您自己没有定义它们;也就是说,编译器将隐式生成定义。)