C++ - 析构函数只是释放内存还是实际删除对象?

4
为了检查这个问题,我运行了以下小测试代码。
#include <iostream>
using namespace std;

class name {
public:
  int data;
  name(int data) {
    this->data = data;
  }
  void printData() {
    cout << data << endl;
  }
};

int main() {
  name* namePointer;
  {
    name n(5);
    namePointer = &n;
  }
  namePointer->printData();
}

所以,有两个问题:
  1. The name object n is created in the block inside main and its pointer is saved, to make sure its destructor is called when we get out of the block. But the pointer points to the same object and its member functions are still accessible. Doesn't this mean that the object isn't deleted yet?

  2. Say I add this to my name class:

    ~name() {
        cout << "destructor called" << endl;
    }
    

    Does overriding the destructor function, and doing nothing in it (here in ~name()), prevent the object from being deleted?

编辑:感谢回复,真的很有帮助。但是我可以知道这些踩票的原因吗?我认为这是一个非常好的问题。¯\_(ツ)_/¯


析构函数销毁和清理对象。它不会释放内存。这由一个单独的机制管理,在析构函数之外。 - François Andrieux
2
我建议阅读这个链接:https://isocpp.org/wiki/faq/dtors - Fred Larson
我不同意重复问题。它们谈论在对象被删除后访问该对象的情况,但问题是“析构函数只是释放内存还是实际上删除了对象”。所提供的重复问题没有回答这个问题。 - SergeyA
@Slava,这个问题的答案如何回答“析构函数是否释放内存”的问题?(抱歉使用了头韵) - SergeyA
@SergeyA 它完美地回答了“这是否意味着对象尚未被删除”,并解释了为什么 OP 观察到这种行为。 - Slava
显示剩余5条评论
4个回答

5
析构函数只是调用子对象的析构函数(如果有),执行析构函数体(如果定义了)。从技术上讲,析构函数并不是释放内存的函数。析构函数可以在不释放内存的情况下被调用,也可以在不调用析构函数的情况下释放内存。当使用delete运算符删除动态对象时,将调用析构函数,然后释放内存。同样,当自动变量(如n)的生命周期结束时,将调用其析构函数,并释放内存。不清楚您所说的“删除对象”的含义。析构函数永远不会调用delete运算符(除非子对象的析构函数体调用delete运算符,但这时被删除的不是this)。如果您的意思是析构函数是否“销毁”对象,则情况正好相反:当对象被销毁时,将调用其析构函数。
指针指向对象原来存在的位置。该对象已被销毁,不存在了。成员函数的可访问性不取决于指向什么。对不指向对象的指针进行解引用(包括调用成员函数)具有未定义的行为。
不能从未定义的行为中得出结论。这不是重写。重写是一个涉及继承和虚函数的特定语言术语。你所做的是定义一个析构函数。在析构函数的函数体中什么也不做(在~name()中),与未定义析构函数时隐式析构函数的作用完全相同。它不会“防止对象被删除”。

4
在C++中,有两个不同的概念“对象生命周期”和“存储期”。存储期与生命周期基本相同,但标准在此处使用了两个不同的单词。存储指对象所占用的物理内存,而对象生命周期则是指允许访问对象的时间。
因此,我们必须理解构造函数和析构函数决定了对象的生命周期 - 在调用构造函数之前和析构函数之后都不能访问对象,并且不涉及存储生命周期。很明显,存储期(或生命周期)不应该小于对象的生命周期 - 无法访问没有物理表示的对象,但可以比对象寿命更长。
当使用“placement new”和显式析构函数调用时,很容易看出存储仍然可用,但对象已经消失。

我会只说“内存”,而不是“物理内存”,因为大多数情况下它是虚拟内存(而不是物理RAM)。 - Basile Starynkevitch
@BasileStarynkevitch,深入探讨了太多。有人可能会认为虚拟内存在某种意义上也是物理的,因为它由RAM(或任何其他内存,如MMC)或交换支持 - 这两者都非常物理。我的观点是存储是物质的,这就是说。我不知道如何恰当地表达这一点,恐怕我们无法真正定义“物质”是什么意思。 - SergeyA

1

1 ... 这难道不意味着对象还没有被删除吗?

不是。

2 ... 覆盖析构函数并在其中什么也不做(即在 ~name() 中),能够防止对象被删除吗?

不是。

cout 是你的朋友。

这是我对你的代码的版本(我已经应用了我的命名规范)。

#include <iostream>

class Name_t
{
   int m_data;
public:
   Name_t(int data) : m_data(data) {
      std::cout << "\n  Name_t::ctor" << std::endl;
   }
   ~Name_t() {
      std::cout << "\n  Name_t::dtor" << std::endl;
   }

   void cout() { std::cout << "\n  Name_t.cout() "
                           << m_data << std::endl; }
};

class T517_t
{
public:
   T517_t() = default;
   ~T517_t() = default;

   int exec()
      {
         Name_t* Name_tPointer = nullptr;
         {
            Name_t n(5);
            Name_tPointer = &n;
         }
         Name_tPointer->cout();
         return(0);
      }    
}; // class T517_t

int main(int , char** )
{
   int retVal = -1;
   {
      T517_t   t517;
      retVal = t517.exec();
   }
   return(retVal);
}

运行此代码,您将在 UB(未定义的行为)之前看到 ctor 和 dtor 均已运行。
  Name_t::ctor

  Name_t::dtor

  Name_t.cout() 5

另一个指标(通过cout)是在析构函数中清除或使数据无效...我很少这样做,除非进行诊断。

将dtor替换为

   ~Name_t() {
      m_data = -1;
      std::cout << "\n  Name_t::dtor" << std::endl;
   }

你可以看到,析构函数已经使你的数据无效化。
  Name_t::ctor

  Name_t::dtor

  Name_t.cout() -1

析构函数是否会释放内存?

释放哪部分内存?

不会 - 对象的析构函数不会释放对象本身。

会 - 对象的析构函数应该释放在构造函数中分配的内存(阴阳两面),除非该实例已经被其他方法删除。


UB - 在这种情况下,它是使用悬空指针,即指向已被析构的对象的指针。 - 2785528

1

在该代码块之后使用namePointer是未定义的行为,因为它是一个悬空指针。在不同的机器、编译器等情况下,它的行为可能会发生改变(正确或不正确)。

覆盖析构函数以输出一些文本不会改变对象的内存仍然被释放的事实。因此,在这种情况下,n的析构函数将在块的末尾被调用,它将删除对象及其成员变量。


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