RAII, value classes and implicit conversion

Chris Nicholson-Sauls ibisbasenji at gmail.com
Tue Nov 14 13:12:31 PST 2006


Boris Kolar wrote:
> Proposed extensions, that should solve most RAII (and other) problems:
> 
> 1 Structs
> =========
> 
> 
> Structs are a lot like classes, except they can NOT:
> - have virtual methods
> - override inherited methods
> (technically: they can't have VMT table)

While in a certain sense true... I'd really like to avoid the C++ situation where the only 
difference is where they get allocated.  I know that's not quite what you're trying for 
here, but one thing could lead to another... that said -- continue;

> 
> 1.1 Immutable structs
> ---------------------
> 
> In addition to normal structs, immutable structs can be defined with:
> 
>   const struct Point {
>     int x;
>     int y;
>   }

Not a bad idea, although one could already accomplish this (I think) just by declaring the 
variable as const, rather than the type... which is notably more consistant.

> They can be constructed as:
> 
>   void test() {
>     Point p = Point(10, 20); // x = 10, y = 20
>     Point p = Point(x: 10, y: 20); // same as above
>     p.x = 30; // COMPILER ERROR: Point is immutable
>   }

If we can get static struct initializers into the open the same way we've gotten array 
literals out of static array initializers, then this wouldn't be needed.  Instead we could 
just:

# Point p1 = {10, 20} ;
# Point p2 = {x:10, y:20} ;

Already works, so long as p is declared static/const.

> 
> 1.2 Structs can have constructor(s) and destructor
> --------------------------------------------------
> 
> Also, structs may have constructor(s), destructor:
> 
>   struct File {
>     this() {
>       printf("File.this");
>     }
>     this(char[] name) {
>       handle = openFile(name);
>       // handle can be modified only in constructor
>     }
>     ~this() {
>       closeFile(handle);
>     }
>     HANDLE handle;
>   }

I'm still undecided on this, to be honest.  I don't think its a bad thing, per se, and 
certainly know of uses for it... but it makes me antsy for some reason.  May just be 
unreasonable paranoia.

> Invocation is deterministic (at the beginning and end of variable scope):
> 
>   void test() {
>     File f; // prints "File.this" (default constructor is invoked)
>     // destructor of f is called here
>   }

As it likely should be.

> 
> 1.3 Inheritance and implicit conversions of structs
> ---------------------------------------------------
> 
> Structs can inherit from one or more other structs:
> 
>   struct A {
>     int a;
>   }
>   struct B {
>     int b;
>   }
>   struct C: A, B {
>     // inherited: int a;
>     // inherited: int b;
>     int c;
>   }

This I would like to see.

> Structs (and classes too) can define implicit conversions:
> 
>   // A may be struct, interface, class, ...
>   struct D: A {
>     // nothing is inherited because implicit conversion is defined
>     this.A {
>       return A(a: 10);
>     }
>   }

If nothing else, it is an interesting syntax.  It might make a nice alternative to the 
opCast() method we have now.  Might even make opCast() obsolete (especially since opCast() 
can only provide conversion to a single type).

> Inheritance and implicit conversion test:
> 
>   void test() {
>     C c;
>     B b = c; // allowed
>     D d;
>     A a = d; // allowed
>   }

I assume that, given 'struct D: A' then the following:
# D d     ; // default ctor
# A a = d ; // allowed; implicit conversion
#   d = a ; // error: cannot convert an A into a D.

Or in other words, upon conversion, it actually /is/ an A, and no longer a D.  Otherwise, 
this could potentially negate some uses of structs as direct maps into data files.

> 
> 2 Classes
> =========
> 
> 
> 2.1 Explicit implementation of interfaces
> -----------------------------------------
> 
> Classes can explicitly implement interface methods or whole interfaces:
> 
>   interface IFoo {
>     void foo();
>   }
>   interface IBar {
>     void bar();
>   }
>   class Foo: IFoo, IBar {
>     // explicitly implement IFoo interface methods
>     void IFoo.foo() {
>     }
>     // explicitly implement interface IBar by delegation
>     this.IBar {
>       return getBar();
>     }
>   }

This I like.  :)

> 
> 2.2 Value classes
> -----------------
> 
> Value classes are like ordinary classes, except:
> - only constructor or destructor can change class state
> - they can not have null value (at least default constructor is called)
> - they have deterministic finalization if they implement 'Local'
> 
> Example:
> 
>   const class Foo: Local {
>     this() {
>       foo = 5; // foo can be modified (only) in constructor/destructor
>     }
>     ~this() {
>       printf("Foo.~this");
>     }
>     int foo;
>   }
>   void test() {
>     Foo foo; // foo.foo = 5 (default constructor is invoked, foo can't be null)
>     // prints "Foo.~this" and destroys foo here
>   }
> 
> Value classes can easily support deterministic finalization, because no cycles are
> possible (without hacking, anyway) and simple reference counting is sufficient.

Hm.  No opinion.

> 
> 2.3 Custom allocators
> ---------------------
> 
> Custom allocators allow any allocation strategy:
> 
>   // in Phobos
>   interface Allocator {
>     void* allocate(Type type);
>     void deallocate(Type type, void* mem);
>   }
>   // test application
>   void test() {
>     Foo foo = new(myAllocator) Foo();
>     // if Foo is value class and implements 'Local', then foo is deleted here
>     // ... else foo is deleted when myAllocator is deleted
>   }

Not a bad idea.  Not sure if I would use it, personally, but not a bad idea.

> 
> 3 Summary
> =========
> 
> 
> It seems that scoped variables are unnecessary, because value classes that
> implement 'Local' interface, together with implicit conversions, are sufficient
> in all of the following scenarios:
> 
> 1. Finalization is required:
> 
>   const class File: Local {
>     ~this() {
>       close;
>     }
>   }

Sure.  Currently achieved using 'auto'... no need to go into that mess all over again, of 
course.

> 2. Garbage collection must be avoided:
> 
>   const class Scoped(T): T, Local {
>     this(T object) {
>       _object = object;
>     }
>     this.T {
>       return _object;
>     }
>     private T _object;
>   }
>   Scoped(T) scoped(T)(T object) {
>     return new Scoped!(T)(object);
>   }
>   class Foo {
>     void foo() { ... }
>   }
>   void test() {
>     Foo foo = scoped(new Foo());
>     foo.foo();
>     // foo is deleted here :)
>   }
>   void test2() {
>     scoped(new Foo()).foo();
>     // Foo instance is deleted here too! :D
>   }

I'm not so sure I understand how this is "avoiding" GC... it seems to be /enforcing/ it. 
But its still a fascinating design concept.

> 3. Value class semantics is desired:
> 
>   const class Point {
>     int x;
>     int y;
>   }

If I want value semantics, I'll generally use a struct.  Just can't think of cases right 
off hand where I'd want this.

> Another (minor) suggestion is allowing method declarations without '()':
> 
>   class Foo: Bar {
>     int foo {
>       // no ambiguity here, consistent with 'this.Bar' definition
>       return 5;
>     }
>     this.Bar {
>       return bar;
>     }
>     private Bar bar;
>   }

Eh, maybe.  I would probably still always type them in there.  They don't take up much 
space, and I'm just too used to a pair of ()'s meaning, "Hey this is a function/method!" 
Also, it makes me think of C# style properties -- and while I would like to see them in D, 
I don't need constant false hope.  ;)

> Also, get rid of static opCall

Just: no.  Leave it be.

-- Chris Nicholson-Sauls



More information about the Digitalmars-d mailing list