【问题标题】:How do I call the class's destructor?如何调用类的析构函数?
【发布时间】:2021-11-26 01:28:50
【问题描述】:

我有一个简单的 C++ 代码,但我不知道如何使用析构函数:

class date {

public:
    int day;
    date(int m)
    {
        day =m;
    }

    ~date(){
    cout << "I wish you have entered the year \n" << day;    
    }
};


int main()
{
  date ob2(12);
  ob2.~date();
  cout << ob2.day;
  return 0;
}

我的问题是,我应该在我的析构函数代码中写什么,调用析构函数后,它会删除day变量

【问题讨论】:

标签: c++ destructor


【解决方案1】:

您很少需要显式调用析构函数。而是在对象被销毁时调用析构函数。

对于像ob2这样的局部变量对象,在超出范围时会被销毁:

int main() 
{ 
    date ob2(12); 

} // ob2.~date() is called here, automatically!

如果使用new 动态分配对象,则在使用delete 销毁对象时调用其析构函数。如果你有一个静态对象,它的析构函数会在程序终止时被调用(如果程序正常终止的话)。

除非您使用new 动态创建某些东西,否则您不需要执行任何显式操作来清理它(因此,例如,当ob2 被销毁时,它的所有成员变量,包括day,被破坏)。如果你动态地创建一些东西,你需要确保它在你完成后被销毁;最佳做法是使用所谓的“智能指针”来确保自动处理此清理。

【讨论】:

    【解决方案2】:

    您不需要显式调用析构函数。这是在对象ob2 的范围结束时自动完成的,即在main 函数结束时。

    此外,由于对象具有自动存储,因此不必删除其存储。这也是在函数结束时自动完成的。

    几乎从不需要手动调用析构函数(仅在低级库代码中),并且仅当先前使用 new 获取内存时(当您使用指针)。

    由于手动内存管理容易发生泄漏,现代 C++ 代码尽量不显式使用 newdelete。当确实需要使用new时,就会使用所谓的“智能指针”来代替常规指针。

    【讨论】:

    • 这一切都很好,但我相信发帖者有一个更基本的问题。
    • 对第三段的一点澄清:使用placement new 时,显式调用析构函数是可以接受的(尽管丑陋且通常可以避免)。
    • @Sydius:这就是我“只在低级库代码中”编写的原因。让我们不要通过抛出更高级的符号来进一步混淆 OP。
    【解决方案3】:

    你不应该显式调用你的析构函数。

    当您在堆栈上创建对象时(就像您所做的那样),您所需要的只是:

    int main()
    {
      date ob2(12);
      // ob2.day holds 12
      return 0; // ob2's destructor will get called here, after which it's memory is freed
    }
    

    当您在堆上创建对象时,您需要在调用其析构函数并释放内存之前delete您的类:

    int main()
    {
      date* ob2 = new date(12);
      // ob2->day holds 12
      delete ob2; // ob2's destructor will get called here, after which it's memory is freed
      return 0;   // ob2 is invalid at this point.
    }
    

    (未能在最后一个示例中调用 delete 将导致内存丢失。)

    这两种方式各有优缺点。堆栈方式非常快地分配对象将占用的内存,您不需要显式删除它,但堆栈空间有限,您无法轻松、快速和干净地移动这些对象。

    堆是执行此操作的首选方式,但在性能方面,它的分配速度很慢,您必须处理指针。但是您对对象的操作具有更大的灵活性,进一步使用指针会更快,并且您可以更好地控制对象的生命周期。

    【讨论】:

    • “堆是首选的方式”——做什么?存储对象?因为那是错误的:堆栈绝对是 C++ 中这样做的首选方式。
    • 使用库并在它们之间传递对象最好(不,至关重要)将它们分配在堆上。肯定堆栈是快速和肮脏的,在某些情况下是有意义的。但是,当您有想要使用、传递、移动、复制和引用计数的对象时,堆栈就不再适合它们了。直到 C++0x 移动语义变得司空见惯……
    • 抱歉,这是错误的。对于参数传递,使用 (const) 引用。对于返回值——只需复制。不会有什么不好的事情发生。参见例如cpp-next.com/archive/2009/08/want-speed-pass-by-value 库永远不应该需要堆分配的参数。这将是一个非常糟糕的库设计。堆栈分配没有什么快速和肮脏的。
    • 完全可以;我完全同意,但这确实无法回答海报的基本问题。我相信我的回答会让他更好地了解如何“使用”析构函数(因为这是他缺乏的基本而简单的概念)以及它​​是如何工作的。它可能不是参考正确的,也不会经得起语言律师的考验,但我认为他会通过 simple 示例更好地了解对象的生命周期。这就是为什么我认为詹姆斯的帖子在表面上仍然很完美。
    • 如果我在循环中创建对象,将每个对象保存到文件中,那么正确的方式是堆方式并在每次迭代的循环结束时调用析构函数?
    【解决方案4】:

    只有在非常特殊的情况下才需要直接调用析构函数。默认情况下,当您创建一个自动存储的变量并且它超出范围或使用new 动态分配的对象被delete 销毁时,系统将调用析构函数。

    struct test {
       test( int value ) : value( value ) {}
       ~test() { std::cout << "~test: " << value << std::endl; }
       int value;
    };
    int main()
    {
       test t(1);
       test *d = new t(2);
       delete d;           // prints: ~test: 2
    }                      // prints: ~test: 1 (t falls out of scope)
    

    为了完整起见,(通常不应使用)调用析构函数的语法类似于方法。析构函数运行后,内存不再是该类型的对象(应该作为原始内存处理):

    int main()
    {
       test t( 1 );
       t.~test();            // prints: ~test: 1
                             // after this instruction 't' is no longer a 'test' object
       new (&t) test(2);     // recreate a new test object in place
    }                        // test falls out of scope, prints: ~test: 2
    

    注意:在t 上调用析构函数后,该内存位置不再是test,这就是重新创建 对象通过placement new 的原因.

    【讨论】:

    • 分配用于放置 new 的内存的通常方法是使用 malloc(或 new 与数组)。这是我在堆栈变量中看到的第一个示例。顺便说一句,在第一个示例中,它将是 test *d=new test(2);
    • @VJo:我开始编写一个带有堆栈分配内存缓冲区的示例,但这需要使用placement new before调用析构函数(小问题)并且没有显示多次调用析构函数的潜在问题。新标准中还有另一个 auto 分配示例:对联合成员的要求较低,它允许对象具有非平凡的构造函数/析构函数。在这种情况下,由用户显式调用联合的活动成员的构造函数和析构函数。
    【解决方案5】:

    尽管析构函数看起来像是你在使用完对象后需要调用它来摆脱或“销毁”你的对象,但你不应该那样使用它。

    析构函数是在您的对象超出范围时自动调用的东西,也就是说,当计算机离开您实例化对象的“花括号”时。在这种情况下,当您离开 main() 时。你不想自己调用它。

    【讨论】:

      【解决方案6】:

      您可能会对这里未定义的行为感到困惑。 C++ 标准没有规定如果在对象的析构函数运行后使用对象会发生什么,因为这是未定义的行为,因此实现可以做任何它喜欢的事情。通常,编译器设计者不会为未定义的行为做任何特别的事情,所以发生的事情是其他设计决策的产物。 (这有时会导致非常奇怪的结果。)

      因此,一旦您运行了析构函数,编译器就没有关于该对象的进一步义务。如果您不再提及它,那也没关系。如果你确实引用了它,那是未定义的行为,从标准的角度来看,行为并不重要,而且由于标准没有说明,大多数编译器设计者不会担心程序的作用。

      在这种情况下,最简单的做法是让对象保持不变,因为它没有占用资源,并且它的存储空间是作为启动函数的一部分分配的,并且在函数退出之前不会被回收.因此,数据成员的值将保持不变。编译器在读取ob2.day 时自然会做的是访问内存位置。

      与任何其他未定义行为的示例一样,结果可能会随着环境的任何变化而变化,但在这种情况下可能不会。如果编译器能够捕获更多未定义行为的情况并发出诊断,那就太好了,但是编译器不可能检测到所有未定义的行为(有些发生在运行时),而且他们通常不会检查他们认为不存在的行为可能。

      【讨论】:

        【解决方案7】:

        在这种情况下,您的析构函数不需要删除 day 变量。

        你只需要在你用 new 分配的内存上调用 delete。

        如果您使用 new 和 delete 来触发调用析构函数,您的代码将如下所示

        class date {
        
          public: int* day; 
          date(int m) { 
              day = new int;
              *day = m; 
          }
        
          ~date(){ 
              delete day;
              cout << "now the destructor get's called explicitly";
          } 
        };
        
        int main() { 
          date *ob2 = new date(12); 
          delete ob2;
          return 0; 
        }
        

        【讨论】:

        • 除了代码在堆上分配内存以存储整数之外,样式还有什么问题?
        • 这就是我的意思。但另外(正如我在下面的帖子中指出的那样),在现代 C++ 中,new 与原始指针的任何使用都有些过时,因为它会导致很多麻烦。当然适合初学者。
        • 代码有一个额外的问题是没有正确定义复制构造函数和复制赋值运算符,这对于初学者来说是一个非常常见的陷阱。
        猜你喜欢
        • 1970-01-01
        • 2012-07-24
        • 2020-08-25
        • 2014-05-17
        • 1970-01-01
        • 2021-07-28
        • 1970-01-01
        • 2017-07-12
        相关资源
        最近更新 更多