RAII, value classes and implicit conversion

Boris Kolar boris.kolar at globera.com
Tue Nov 14 04:21:00 PST 2006


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)


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
  }


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

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
  }


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

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

Inheritance and implicit conversion test:

  void test() {
    C c;
    B b = c; // allowed
    D d;
    A a = d; // allowed
  }


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


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.


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
  }


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

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
  }

3. Value class semantics is desired:

  const class Point {
    int x;
    int y;
  }

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

Also, get rid of static opCall



More information about the Digitalmars-d mailing list