Sense check: construction / deconstruction

Steven Schveighoffer schveiguy at yahoo.com
Tue Apr 24 23:49:14 UTC 2018


On 4/24/18 6:59 PM, Jordan Wilson wrote:
> I have the following code:
> import std.stdio;
> import std.typecons;
> import d2sqlite3;
> 
> class A {
>      Database db;
>      this ( Database d) {
>          db = d;
>      }
> }
> 
> class B {
>      Database* db;
>      this ( Database* d) {
>          db = d;
>      }
> }
> 
> void main() {
>      auto db = Database(":memory:");
>      auto a = new A(db); // gives message:
>                          // Error: clean-up of Database incorrectly
>                          // depends on destructors called by the GC
> 
>      auto b = new B(&db); // no message
>      auto c = scoped!A(db); // no message
> }
> 
> Assumption 1: "a" gives me an error message due to the fact that proper 
> clean up of db depends on a being collected by the GC, and this behavior 
> is being dis-allowed through use of the idiom 
> https://p0nce.github.io/d-idioms/#GC-proof-resource-class?
> The relevant function calling the error message is:
> void ensureNotInGC(T)(string info = null) nothrow
> {
>      import core.exception : InvalidMemoryOperationError;
>      try
>      {
>          import core.memory : GC;
>          cast(void) GC.malloc(1);
>          return;
>      }
>      catch(InvalidMemoryOperationError e)
>      {
>          // error message here
>      }
> }
> 
> Assumption 2: "b" gives me no error messages because the class B uses 
> pointers, which moves it from relying on GC, to being manually free?
> 
> Assumption 3: "c" gives me no error messages because...well, I don't 
> really understand why, maybe because c is in the same scope as db?

What you are missing is that Database is pass-by-value, not a class. So 
when you include it directly in a class like you did in A, then when A's 
destructor is called, db's destructor is called.

Since in the first case, a is being destroyed by the GC, you get the error.

In the second case (b), you aren't including the db by value, so no 
destructor is called from the GC. But this is dangerous, because db 
stops existing after main exits, but b continues to exist in the GC, so 
this is a dangling pointer.

In the third case, scoped specifically destroys c when main exits, and 
you are not in the GC at that point.

What the error message is telling you is you should manually clean up 
the database directly instead of leaving it to the GC. What is the 
correct path? probably the scoped!A version. Though I'm not sure what 
making copies of the database does in that library.

-Steve


More information about the Digitalmars-d-learn mailing list