【问题标题】:Destructor not called when type-casted void pointer to object将类型转换为指向对象的 void 指针时未调用析构函数
【发布时间】:2008-12-17 09:08:54
【问题描述】:

样本

void func(void* data)
{
 CResource* resource = (CResource*)data;
 delete resource; // ~CResource never called.
 resource = NULL;
}

请帮我解决这个问题。

【问题讨论】:

  • 必须调用析构函数。请提供 CResource 的定义。
  • 可以像 data==0 一样简单。但我们需要代码来决定会发生什么:)
  • 该问题显示对基本原理缺乏了解,但尝试询问更高级的原理。
  • ... 无论如何,它的做法很糟糕——代码不够完整,任何人都无法给出答案,因此需要水晶球猜测。
  • 不,不是,只有一个原因不会在这样的函数中调用析构函数,我在回答中指出了这个原因。我同意,问题(标题)可能应该改变。

标签: c++


【解决方案1】:

总结了CResource析构函数可能不被调用的可能原因,摘自其他答案:

类型不完整

一个可能的原因是您只声明了 CResource 类型,没有定义:

class CResource;

void func(void* data)
{
 CResource* resource = (CResource*)data;
 delete resource; // ~CResource never called.
 resource = NULL;
}

这是一个未定义的行为(删除不完整的类型)。在这种情况下,编译器应该发出关于未调用析构函数的警告(Visual C++ 肯定会发出它)。如果是这种情况,请确保在要销毁它的地方定义了类型(包括必需的标头)。

空指针

如果 data 为 NULL,delete 什么都不做,也不调用任何析构函数。

类型不匹配

如果 CResource 析构函数是虚拟的,并且存储在 data 指向的内存中的对象实际上是不同的类型,则会得到未定义的行为。通常会调用不同的析构函数(如果对象有另一个虚拟析构函数),在其他情况下程序可能会崩溃(如果对象没有虚拟析构函数)。

【讨论】:

  • 用new创建对象时,堆上创建的对象会加上类型信息吗?
  • 在使用new创建对象时,确实会在堆上创建的对象中添加类型信息,但前提是该对象属于具有至少一个虚拟方法的类。
  • 析构函数调用所需的类型信息是静态的(编译时)。当未定义类型时,编译器不知道析构函数是否存在,也不知道析构函数是什么(是否为虚拟)。
  • 链接器不会解决这个问题吗?我的意思是,析构函数总是存在的(编译器会生成默认的,如果你不提供),所以编译器可以安全地假设析构函数存在,并且链接器会找到它。至少,这就是理解孔编译器-链接器关系的方式......
  • std 明确指出,如果完整类型具有非平凡的析构函数(用户定义),则对不完整类型的指针进行删除是未定义的行为。
【解决方案2】:

不调用析构函数的唯一原因是数据指针是否为 0(或 NULL)。这就是 delete 的工作原理 - 它检查指针是否不为 0,如果不是 - 它调用必要的析构函数并释放内存。

正如评论中指出的那样。它不会被调用还有另一个原因。如果数据指向某个其他类(不是 CResource)对象并且两个类都有虚拟析构函数,则将调用该其他类的析构函数。

【讨论】:

  • 或者如果数据指向 CResource 以外的东西,并且 CResource 具有虚拟析构函数。 delete 将尝试从“坏”对象中使用 VMT,并可能随机调用其他对象。
【解决方案3】:

析构函数是虚拟的吗?也许 data 根本没有指向 CResource 对象,并且正在调用某个其他类的虚拟析构函数。

【讨论】:

    【解决方案4】:
    class Aardvark
    {
    public:
        virtual ~Aardvark()
        {
            printf("Aardvark::~Aardvark\n");
        }
    };
    
    class CResource
    {
    public:
        virtual ~CResource()
        {
            printf("CResource::~CResource\n");
        }
    };
    
    void func(void* data)
    {
        CResource* resource = (CResource*)data;
        delete resource; // ~CResource never called.
        resource = NULL;
    }
    
    int _tmain()
    {
        void *data = new Aardvark();
        func( data );
        return 0;
    }
    

    【讨论】:

    • 只是一个问题。如果类有更多的虚函数,析构函数将是一个类中的第一个虚函数,第二个类中的第五个。 Aardvark 的析构函数还会被调用吗?还是会调用其他虚拟成员之一?
    • 我刚刚用我的样本进行了尝试。由于析构函数是 CResource 中的第一个虚函数,这段代码实际上调用了 Aardvark 的 vtable 中的第一个函数,无论它是否是析构函数。我猜这可能属于“未定义行为”的标题。
    • 这取决于编译器如何实现它。在 Visual Studio 中,析构函数始终是虚函数表中的第一个条目。
    • 非常有趣...可能会出现细微的错误,即使您使用的是同一个类,并且您更改了函数的顺序并且没有重新构建整个项目...非常有趣。 ..
    【解决方案5】:

    调用了析构函数。给我们完整的代码。

    【讨论】:

      【解决方案6】:

      你为什么说不调用 dtor? 可能是因为您在 dtor 中添加了 printf,但没有看到任何消息?

      可能调用了另一个dotr? CResource 是继承的吗?你定义了基类的析构函数 virtual 吗?

      正如 rajKumar 所指出的,请将完整代码提供给我们,我们会尽力帮助您。

      【讨论】:

        【解决方案7】:

        我假设 CResource 是某种超类,您可以通过通用指针轻松传输对象。

        那么你的问题对于 C++ 新手来说是一个正常的问题,你应该阅读

        http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7

        此外,请获取 Scott Meyer 的“Effective C++”的副本,因为它会向您介绍接下来几个月您将犯的许多错误。

        【讨论】:

          猜你喜欢
          • 2021-07-22
          • 1970-01-01
          • 2014-01-13
          • 2021-09-20
          • 2013-10-10
          • 1970-01-01
          • 2011-02-06
          • 1970-01-01
          相关资源
          最近更新 更多