RAII, value classes and implicit conversion

Bill Baxter dnewsgroup at billbaxter.com
Tue Nov 14 18:11:59 PST 2006


Boris Kolar wrote:
> Proposed extensions, that should solve most RAII (and other) problems:

Heh heh.  That's a mighty big claim.  :-)

> 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)
> 
> 
> 1.1 Immutable structs
> ---------------------
> 
> In addition to normal structs, immutable structs can be defined with:
> 
>   const struct Point {
>     int x;
>     int y;
>   }
> 
> 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
>   }

I don't see much point in that really.
hahahah get it?  Not much Point.  yuk yuk yuk yuk yuk yuk.
I'll be here all week.

But seriously const applied to instances seems sufficient to me.  Why 
restrict the utility of the class in that way?  At least you need to 
find a better example than Point to illustrate your case.

> 1.2 Structs can have constructor(s) and destructor
> --------------------------------------------------
> 
> Also, structs may have constructor(s), destructor:

I'd like this too -- at least constructors.  But Walter is against 
destructors since (IIUC) it creates complications when passing structs 
as arguments or when they get created as implicit temporaries by the 
compiler.  Something like that.

Anyway, people use static opCall as a constructor now, but it's always 
annoying to write such a beast (or such beasts since you often write 3 
or 4 different versions):

     static MyStruct opCall(S s, T t, R r) {
         MyStruct c; with (c) {
            m_svar = s;
            m_tvar = t;
            m_rvar = r;
         } return c;
     }

That's about as minimal as it can be, but it's still two superfluous 
lines and a superfluous return type that you wouldn't need in a real 
constructor.  And it may be slightly less efficient if the compiler 
doesn't optimize assignment well.  It could mean a create & copy vs just 
one in-place creation.  And it doesn't declare your intent clearly. 
It's just a convention.  static opCall doesn't necessarily return a new 
instance of the struct.

However, if you add constructors to structs what's the syntax for use 
going to be?  Personally I can't think of anything more natural than 
MyStruct(a,b,c).    So I think it would have to act just like a static 
opCall.  The only difference is you'd be able to write this instead of 
the above with all it's extra cruft:

     this(S s, T t, R r) {
            m_svar = s;
            m_tvar = t;
            m_rvar = r;
     }

Destructors I don't know about.  The main use case I have is little 
sentinal structs that do "set_state" in the constructor "reset_state" in 
the destructor.  (A lot of use for that in OpenGL programs).

The thing is I think that paradigm is not so clear.  The 
"fire-and-forget" nature of it is useful when programming

{
    auto st = scopedTransform(trans);
    ...
    {
       auto sc = scopedColor(color);
       ...
    }
}

but it's easy for those ... parts to get very long, and it's easy for 
those scopeTransform thingy's to get buried somewhere in the middle of 
the scope.  And then it's easy for someone to come along and see what 
looks like a superfluous block and decide to delete the extra {}, 
creating a hard to find bug.

So I actually think something like this may be a better style for D:

   auto st = tempTransform(trans); scope(exit) st.restore;

At least then the maintenance programmer can see clearly that there's a 
scope exit in the block and thereby know not to delete that extra {}. 
Plus that's currently valid D code.

>   struct File {

Something like a File shouldn't be a struct.  If you're going to be 
doing file IO, then probably the speed hit of having to do one dynamic 
allocation is not important.


> 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;
>   }

Yeh, this has been suggested before.  There was a discussion about it 
fairly recently.  I don't see why this shouldn't be possible.


> 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);
>     }
>   }

Don't know about that.  I think implicit conversion would only make 
sense for pointers to structs.  But maybe I'm missing your point.

> 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();
>     }
>   }

So this is just an alternative to 'override'?  If so I like it.  I find 
I always end up putting a comments in my code anyway like:

   /// These implement the IImplementable interface
   override int foo() {...}
   override int bar() {...}

But if I move around methods those comments can get stale.

   int IImplementable.foo() { ... }
   int IImplementable.bar() { ... }

would be clearer and eliminate the need for override in most cases. 
Problem is I think (even though rare) an override can override a method 
declared in multiple different interfaces.


> 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.

This is just different syntax for "auto class Foo" as far as I can tell.

> 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
>   }

I thought this was possible already.


> Another (minor) suggestion is allowing method declarations without '()':

Eh, I don't that as much of a gain, and it would make grepping for 
function definitions a little bit harder.

--bb



More information about the Digitalmars-d mailing list