www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Struct destructor

reply JN <666total wp.pl> writes:
Compare this D code:

import std.stdio;

struct Foo
{
     ~this()
     {
         writeln("Destroying foo");
     }
}

void main()
{
     Foo[string] foos;

     foos["bar"] = Foo();
     writeln("Preparing to destroy");
     foos.remove("bar");
     writeln("Ending program");
}

and equivalent C++ code:

#include <iostream>
#include <map>
#include <string>

using namespace std;

struct Foo
{
     ~Foo()
     {
         cout << "Destroying foo" << endl;
     }
};

int main()
{
     map<string, Foo> foos;

     foos["bar"] = Foo();
     cout << "Preparing to destroy" << endl;
     foos.erase("bar");
     cout << "Ending program" << endl;
}


C++ results in:

Destroying foo
Preparing to destroy
Destroying foo
Ending program

D results in:

Preparing to destroy
Ending program
Destroying foo

Is this proper behavior? I'd imagine that when doing 
foos.remove("bar"), Foo goes out of scope and should be 
immediately cleaned up rather than at the end of the scope? Or am 
I misunderstanding how should RAII work?
Mar 02 2019
next sibling parent Matheus <m mail.com> writes:
On Saturday, 2 March 2019 at 11:32:53 UTC, JN wrote:
 ...
 Is this proper behavior? I'd imagine that when doing 
 foos.remove("bar"), Foo goes out of scope and should be 
 immediately cleaned up rather than at the end of the scope? Or 
 am I misunderstanding how should RAII work?
https://dlang.org/spec/struct.html#struct-destructor "Destructors are called when an object goes out of scope. Their purpose is to free up resources owned by the struct object." Example: import std.stdio; struct S { ~this() { import std.stdio; writeln("S is being destructed"); } } void main(){ { S s = S(); scope (exit) { writeln("Exiting scope"); } } writeln("Ending program."); }
 Output:
Exiting scope S is being destructed Ending program. Matheus.
Mar 02 2019
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, March 2, 2019 4:32:53 AM MST JN via Digitalmars-d-learn wrote:
 Compare this D code:

 import std.stdio;

 struct Foo
 {
      ~this()
      {
          writeln("Destroying foo");
      }
 }

 void main()
 {
      Foo[string] foos;

      foos["bar"] = Foo();
      writeln("Preparing to destroy");
      foos.remove("bar");
      writeln("Ending program");
 }
 D results in:

 Preparing to destroy
 Ending program
 Destroying foo

 Is this proper behavior? I'd imagine that when doing
 foos.remove("bar"), Foo goes out of scope and should be
 immediately cleaned up rather than at the end of the scope? Or am
 I misunderstanding how should RAII work?
foos is an associate array. Its memory is managed by the GC. As such, any objects in the AA will only have their destructors run when the GC collects the memory. If no collection happens, then the destructors will never actually be run. Given that you're seeing the output from the destructor after main executes, it looks like the GC is currently set up to do a collection when the program terminates (though that's not actually guaranteed to happen by the language). If no collection were run, then you'd just see Preparing to destroy Ending program But either way, remove doesn't trigger a collection. It has no reason to. Whatever memory the AA has which contains the Foo is either still referenced by the AA after remove is called, or it's unreferenced but hasn't been collected by the GC, because no collection has been run. It wouldn't surprise me if the AA actually overwrote the Foo with Foo.init when remove was called, in which case, opAssign would have been called if you'd defined it, but you didn't, and I'm not familiar with the AA implementation's internals, so I don't know exactly how it currently works. But regardless of whether the AA would have reused the memory, or whether it was no longer referenced and was just waiting for the GC to run a collection, without the GC running a collection, the destructor would not be run. Since the GC ran a collection as part of the program's shutdown after main ran, the memory was collected at that point, and the destructor was run, causing the destructor's message to be printed after main terminated. The GC heap really is no place for objects that need deterministic destruction, because you don't normally know when the GC will run a collection. It works well enough if you don't care when the object gets collected, but if you really need deterministic destruction, then the object needs to be on the stack, or you need to manage its memory manually, with its memory being allocated by something other than the GC. - Jonathan M Davis
Mar 02 2019