Best way to manage non-memory resources in current D, ex: database handles.

Adam D. Ruppe via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Wed Mar 8 16:47:48 PST 2017


On Wednesday, 8 March 2017 at 23:54:56 UTC, Chad Joan wrote:
> What's the best way to implement such a range in current D?

I'd go with a struct with disabled copying and default 
construction, then make the destructor free it and the function 
that returns it populate it.

So basically Unique.

> The destroy function doesn't mention what methods specifically 
> will be executed on the target object, other than "destructor 
> or finalizer".

It calls the `~this()` function, aka the destructor. It is just 
sometimes called a finalizer in other contexts too. Same thing, 
different name.


> So I have similar questions about this as I did Unique: How 
> does it "free" the resource T?  What method does it call to 
> tell T to deallocate itself?

So it doesn't deallocate itself, but it can deallocate its 
members.

It calls ~this(); first, your destructor, and you can free its 
members with that. Then it calls `free()` on the outer object 
pointer itself.

So clean up the members in the destructor and you should be good. 
Basically the same deal as with Unique.

> I'm leaning towards this methodology;

This is good, but it is easy to forget the scope(exit) too.

That said, this is how my database.d handles its connection 
classes. If your connection is a struct, using the destructor is 
better (your option #3), since it just does this automatically - 
a struct destructor (unless it is in a dynamic array or some 
other kind of pointer) is automatically called on scope exit.

Classes, though, do not get their dtors called then - they wait 
until they are GC'd - so scope(exit) does a good job with getting 
them cleaned up faster.

> ===  (3)  Put a deallocate() method; call it in ~this()


This is what my database.d does with the query results. The 
database connection class is polymorphic and thus doesn't work 
well as a struct, but the query result worked beautifully as a 
struct and the destructor handles it.

I also threw in `@disable this(this);` to ensure it isn't copied 
somewhere so I don't have to refcount it or anything annoying 
like that. On the other hand, I must consume the query in-place 
(or pass it by pointer to other functions being careful not to 
keep it after the outer function returns)... but that's what I 
want to do anyway.



So my code looks something like this:


class Database {
    this(string conn) {
         this.handle = establish_connection(conn);
         if(this.handle is null) throw new Exception();
    }

    ~this() {
         close_connection(this.handle);
    }

    Result query(string sql, string[] args) {
          // hugely simplified
          auto q = prepare_query(sql);
          bind_args(q, args);

          // the Result is constructed here and only here
          return Result(execute_query(q));
    }
}

struct Result {
      // no business default constructing it ever
      @disable this();

      // forbid copying it. Instead, pass it by pointer
      // or just loop it in-place and copy the results
      // elsewhere for storage.
      @disable this(this);

      // private constructor since only the query
      // method above should be making these
      private this(c_query_handle handle) {
         this.handle = handle;
          // do whatever other allocation needs
          // to be done via C functions
         // ....

         popFront(); // prime the first result
      }

      ~this() {
          // destroy whatever C resources this holds
          destroy_query_handle(this.handle);
      }

      Row front() {
         return makeDFriendly(this.current_row);
      }

      void popFront() {
         this.current_row = fetch_next_row(this.handle);
      }

      bool empty() {
         return has_more_rows(this.handle);
      }
}



Then use it like this:


void main() {
     auto db = new Database("db=test");
     scope(exit) .destroy(db);

     foreach(row; db.query("select * from foo", null)) {
         // work with row
     }
}



Now, if you forget to scope(exit), it is OK, the garbage 
collector WILL get around to it eventually, and it is legal to 
work with C handles and functions from a destructor. It is only 
illegal to call D's garbage collector's functions or to reference 
memory managed by D's GC inside the destructor. C pointers are 
fine.

It is just nicer to close the connection at a more specified time.


More information about the Digitalmars-d-learn mailing list