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