Jump to page: 1 24  
Page
Thread overview
"Value class instance" pattern?
Jul 13, 2013
bearophile
Jul 13, 2013
Benjamin Thaut
Jul 13, 2013
Benjamin Thaut
Jul 13, 2013
bearophile
Jul 13, 2013
Benjamin Thaut
Jul 13, 2013
Benjamin Thaut
Jul 13, 2013
bearophile
Jul 14, 2013
Benjamin Thaut
Jul 14, 2013
bearophile
Jul 13, 2013
bearophile
Jul 14, 2013
Benjamin Thaut
Jul 14, 2013
bearophile
Jul 14, 2013
Benjamin Thaut
Jul 13, 2013
Kagamin
Jul 14, 2013
Benjamin Thaut
Jul 14, 2013
bearophile
Jul 14, 2013
Benjamin Thaut
Jul 14, 2013
bearophile
Jul 14, 2013
Benjamin Thaut
Jul 14, 2013
bearophile
Jul 13, 2013
Kagamin
Jul 13, 2013
bearophile
Jul 13, 2013
Kagamin
Jul 13, 2013
bearophile
Jul 14, 2013
Dicebot
Jul 14, 2013
Dicebot
Jul 14, 2013
bearophile
Jul 14, 2013
Namespace
Jul 14, 2013
bearophile
Jul 14, 2013
Namespace
Jul 14, 2013
Dicebot
Jul 14, 2013
Dicebot
Jul 14, 2013
Artur Skawina
Jul 14, 2013
Dicebot
Jul 14, 2013
Artur Skawina
Jul 14, 2013
Dicebot
Jul 14, 2013
Timon Gehr
Jul 14, 2013
Dicebot
Jul 15, 2013
Kagamin
July 13, 2013
I don't know if this post is more fit for the main D newsgroup. In the end I have decided that it's better to ask here first.

Here inside every instance of Bar there is a reference to an instance of Foo:



abstract class Something {
    int spam(int);
}

class Foo : Something {
    int x;
    this(int xx) { x = xx; }
    override int spam(int x) { return x; }
}

class Bar : Something {
    Foo f;
    int y;
    this(Foo ff) { f = ff; }
    override int spam(int x) { return x; }
}

void main() {
    auto b = new Bar(new Foo(1));
}


In C++ class instances are values, so the class Bar can contain an instance of Foo as a value. This removes one allocation and one indirection and decreases a bit the pressure on the GC. How to do the same thing in D? Can I use Scoped or something to do that?

This is one solution I have found, to create a FooValue struct, and use Foo only as a wrapper:


abstract class Something {
    int spam(int);
}

struct FooValue {
    int x;
    this(int xx) { x = xx; }
    int spam(int x) { return x; }
}

class Foo : Something {
    FooValue f1;
    this(int xx) { f1.x = xx; }
    override int spam(int x) {
        return f1.spam(x);
    }
}

class Bar : Something {
    FooValue f2;
    int y;
    this(FooValue fv) { f2 = fv; }
    override int spam(int x) { return x; }
}

void main() {
    auto b = new Bar(FooValue(1));
}


But is this the best solution? (Beside containing some boilerplate code, calling Foo.spam requires a function call and a virtual call, instead of just a virtual call. Can we do with just one virtual call?).


Recently Andrei has suggested to create a Phobos wrapper (not yet implemented), I don't know if it's usable here:
http://d.puremagic.com/issues/show_bug.cgi?id=10404

If that wrapper will also allow derivation (as I have suggested) then I think it will allow shorter code like this (but I think this will keep needing a a function call and a virtual call to call Foo.spam):


import std.typecons: Class;

abstract class Something {
    int spam(int);
}

struct FooValue {
    int x;
    this(int xx) { x = xx; }
    int spam(int x) { return x; }
}

alias Foo = Class!(FooValue, Something);

class Bar : Something {
    FooValue f2;
    int y;
    this(FooValue fv) { f2 = fv; }
    override int spam(int x) { return x; }
}

void main() {
    auto b = new Bar(FooValue(1));
}


Bye and thank you,
bearophile
July 13, 2013
I had the very same problem and I'm using a composite helper struct for this purpose:

struct DefaultCtor {}; //call default ctor type

struct composite(T)
{
  static assert(is(T == class),"can only composite classes");
  void[__traits(classInstanceSize, T)] _classMemory = void;
  bool m_destructed = false;
  debug {
    T _instance;
  }
  else
  {
    @property T _instance()
    {
      return cast(T)_classMemory.ptr;
    }

    @property const(T) _instance() const
    {
      return cast(const(T))_classMemory.ptr;
    }
  }

  alias _instance this;

  @disable this();
  @disable this(this); //prevent evil stuff from happening

  this(DefaultCtor c){
  };

  void construct(ARGS...)(ARGS args) //TODO fix: workaround because constructor can not be a template
  {
    _classMemory[] = typeid(T).init[];
    T result = (cast(T)_classMemory.ptr);
    static if(is(typeof(result.__ctor(args))))
    {
      result.__ctor(args);
    }
    else
    {
      static assert(args.length == 0 && !is(typeof(T.__ctor)),
                    "Don't know how to initialize an object of type "
                    ~ T.stringof ~ " with arguments:\n" ~ ARGS.stringof ~ "\nAvailable ctors:\n" ~ ListAvailableCtors!T() );
    }
    debug _instance = result;
  }

  void destruct()
  {
    assert(!m_destructed);
    Destruct(_instance);
    debug _instance = null;
    m_destructed = true;
  }

  ~this()
  {
    if(!m_destructed)
    {
      Destruct(_instance);
      m_destructed = true;
    }
  }
}


I and I use it often (especially with containers, I always tend to forget newing containers, which can not happen with this helper struct because of @disable this();

Usage is something like the following:

class Foo
{
  int m_i;
  this()
  {
    m_i = 5;
  }

  this(int i)
  {
    m_i = i;
  }
}

class Bar
{
  composite!Foo m_foo;

  this()
  {
    m_foo = typeof(m_foo)();
    m_foo.construct(DefaultCtor()); // would call Foo.this()
    // alternative
    m_foo.construct(5); // would call Foo.this(int)
  }
}

I wrote this before constructors of structs could be templated, I plan on updating it so you can write

m_foo = typeof(m_foo)(5);

The m_destructed member is kind of optional, if the default destruction order is ok, you can omit it and save the additional bytes of overhead it imposes. A advantage is also that Foo is a normal class, you can inherit from it and you don't have to write any boilerplate code to use the value type of Foo.
July 13, 2013
Am 13.07.2013 15:17, schrieb Benjamin Thaut:
> m_foo = typeof(m_foo)();

Sorry this line should read:

m_foo = typeof(m_foo)(DefaultCtor());

there should really be some kind of edit option for the newsgroup.
July 13, 2013
(Sorry, this post has gone in this newsgroup by mistake, but it's a small mistake.)


To change and understand your code a bit (now the ComposeClass constructor is a template) I have removed some of the tests and debug code, but they should be added back later:


/*
string listAvailableCtors(T)() {
    string result = "";
    foreach(t; __traits(getOverloads, T, "__ctor"))
        result ~= typeof(t).stringof ~ "\n";
    return result;
}
*/

struct DefaultCtor {} //call default ctor type
enum defaultCtor = DefaultCtor();

struct ComposeClass(T) if (is(T == class)) {
    void[__traits(classInstanceSize, T)] _classMemory = void;
    bool m_destructed = false;

    @property T _instance() {
        return cast(T)_classMemory.ptr;
    }

    @property const(T) _instance() const {
        return cast(const(T))_classMemory.ptr;
    }

    alias _instance this;

    @disable this();
    @disable this(this);

    this(DefaultCtor) {
        // _classMemory[] = typeid(T).init[]; // needed?
        _instance.__ctor;
    }

    this(Targs...)(Targs args) {
        _classMemory[] = typeid(T).init[];
        _instance.__ctor(args);
    }

    ~this() {
        if (!m_destructed) {
            _instance.destroy;
            m_destructed = true;
        }
    }
}

// Usage example ----------

class Foo {
    int i, j;
    this() {
        this.i = 5;
    }

    this(int ii) {
        this.i = ii;
    }
}

class Bar {
    ComposeClass!Foo f;

    this() {
        //f = typeof(f)(defaultCtor);
        f = typeof(f)(2); // alternative
    }
}

void main() {
    import std.stdio;
    auto bar = new Bar;
    writeln(bar.f.i);
    bar.f.i = 1;
    writeln(bar.f.i);
}


This code is unfinished.
Is the assignment needed in this(DefaultCtor)?

This code contains some sharp edges (for the D programmer and even for the compiler, I have opened a new bug report: http://d.puremagic.com/issues/show_bug.cgi?id=10629 ), for me it's easy to get wrong, hard to get right & good, and I believe it's of general usefulness, so I think it's fit for Phobos.

But isn't it a replacement for Phobos Scoped?

Bye,
bearophile
July 13, 2013
Am 13.07.2013 17:15, schrieb bearophile:
> (Sorry, this post has gone in this newsgroup by mistake, but it's a
> small mistake.)
>
>
> To change and understand your code a bit (now the ComposeClass
> constructor is a template) I have removed some of the tests and debug
> code, but they should be added back later:
>
>
> /*
> string listAvailableCtors(T)() {
>      string result = "";
>      foreach(t; __traits(getOverloads, T, "__ctor"))
>          result ~= typeof(t).stringof ~ "\n";
>      return result;
> }
> */
>
> struct DefaultCtor {} //call default ctor type
> enum defaultCtor = DefaultCtor();
>
> struct ComposeClass(T) if (is(T == class)) {
>      void[__traits(classInstanceSize, T)] _classMemory = void;
>      bool m_destructed = false;
>
>      @property T _instance() {
>          return cast(T)_classMemory.ptr;
>      }
>
>      @property const(T) _instance() const {
>          return cast(const(T))_classMemory.ptr;
>      }
>
>      alias _instance this;
>
>      @disable this();
>      @disable this(this);
>
>      this(DefaultCtor) {
>          // _classMemory[] = typeid(T).init[]; // needed?
>          _instance.__ctor;
>      }
>
>      this(Targs...)(Targs args) {
>          _classMemory[] = typeid(T).init[];
>          _instance.__ctor(args);
>      }
>
>      ~this() {
>          if (!m_destructed) {
>              _instance.destroy;
>              m_destructed = true;
>          }
>      }
> }
>
> // Usage example ----------
>
> class Foo {
>      int i, j;
>      this() {
>          this.i = 5;
>      }
>
>      this(int ii) {
>          this.i = ii;
>      }
> }
>
> class Bar {
>      ComposeClass!Foo f;
>
>      this() {
>          //f = typeof(f)(defaultCtor);
>          f = typeof(f)(2); // alternative
>      }
> }
>
> void main() {
>      import std.stdio;
>      auto bar = new Bar;
>      writeln(bar.f.i);
>      bar.f.i = 1;
>      writeln(bar.f.i);
> }
>
>
> This code is unfinished.
> Is the assignment needed in this(DefaultCtor)?
>
> This code contains some sharp edges (for the D programmer and even for
> the compiler, I have opened a new bug report:
> http://d.puremagic.com/issues/show_bug.cgi?id=10629 ), for me it's easy
> to get wrong, hard to get right & good, and I believe it's of general
> usefulness, so I think it's fit for Phobos.
>
> But isn't it a replacement for Phobos Scoped?
>
> Bye,
> bearophile

Yes the assignment in this(DefaultCtor) is needed, because the construction process of a D object is defined as:

1) Initialize memory with T.init
2) Call the constructor

You can test that with a class that looks as follows

class Foo
{
  int i = 5;
  this() { assert(i == 5); }
}

I really like the constant defaultCtor value idea which makes the usage somewhat nicer. The debugging values are needed because most debuggers won't be able to evaluate properties while debugging.

Yes this looks pretty similar to scoped in phobos but I always thought that scoped was inteded for objects to be placed on the stack instead of inside other objects. Thats also why scoped does some additional alignment which is garantueed when you do it within a class.

Also from reading the sourcecode of scoped, doesn't the address of the scoped object change between construction and destruction? Because it is created inside the scoped function and then returned, which will do a move. And then later destroyed at its final destination. Wouldn't that break with interior pointers?

Kind Regards
Benjamin Thaut

July 13, 2013
I also wanted to mention the "ListAvailableCtors" template which is a nice addition in case there is no constructor available to be called with the given arguments. It will generate a list of all aviable ctors with the types of its arguments, and thus greatly improve the error message given when no appropriate constructor can be found:

string ListAvailableCtors(T)()
{
  string result = "";
  foreach(t; __traits(getOverloads, T, "__ctor"))
    result ~= typeof(t).stringof ~ "\n";
  return result;
}

In my original code it was used during construction like this:

static if(is(typeof(result.__ctor(args))))
{
  result.__ctor(args);
}
else
{
  static assert(args.length == 0 && !is(typeof(T.__ctor)), "Don't know
  how to initialize an object of type " ~ T.stringof ~ " with
  arguments:\n" ~ ARGS.stringof ~ "\nAvailable ctors:\n" ~
  ListAvailableCtors!T() );
}

July 13, 2013
Benjamin Thaut:

> I also wanted to mention the "ListAvailableCtors" template which is a nice addition in case there is no constructor available to be called with the given arguments. It will generate a list of all aviable ctors with the types of its arguments, and thus greatly improve the error message given when no appropriate constructor can be found:
>
> string ListAvailableCtors(T)()
> {
>   string result = "";
>   foreach(t; __traits(getOverloads, T, "__ctor"))
>     result ~= typeof(t).stringof ~ "\n";
>   return result;
> }
>
> In my original code it was used during construction like this:
>
> static if(is(typeof(result.__ctor(args))))
> {
>   result.__ctor(args);
> }
> else
> {
>   static assert(args.length == 0 && !is(typeof(T.__ctor)), "Don't know
>   how to initialize an object of type " ~ T.stringof ~ " with
>   arguments:\n" ~ ARGS.stringof ~ "\nAvailable ctors:\n" ~
>   ListAvailableCtors!T() );
> }

In my version of your code I have just added a template constraint, this is simpler, and it generates an error at the calling point:

    this(Targs...)(Targs args)
    if (__traits(compiles, _instance.__ctor(args))) {
        classInstanceBuf[] = typeid(T).init[];
        _instance.__ctor(args);
    }

Isn't this enough?

Bye,
bearophile
July 13, 2013
Benjamin Thaut:

> Yes the assignment in this(DefaultCtor) is needed, because the construction process of a D object is defined as:

OK.


> The debugging values are needed because most debuggers won't be able to evaluate properties while debugging.

OK.


Is that ClassCompose destructor enough (with something added for the debug build)?

    ~this() {
        if (!isDestructed) {
            _instance.destroy;
            isDestructed = true;
        }
    }

Trying to use ClassCompose in my code I have had some problems caused by const classes and ClassCompose dtor. Maybe such dtor (and isDestructed) can be versioned out for composed-in classes that only contain values...



> Yes this looks pretty similar to scoped in phobos but I always thought that scoped was inteded for objects to be placed on the stack instead of inside other objects.

Right. I think Scoped can't be used for class composition.
But isn't such ClassCompose enough for both purposes? Or is it better to have in Phobos both Scoped and something similar to ClassCompose?


> Thats also why scoped does some additional alignment which is garantueed when you do it within a class.

Maybe such alignment can be added to the ClassCompose too.


> Also from reading the sourcecode of scoped, doesn't the address of the scoped object change between construction and destruction? Because it is created inside the scoped function and then returned, which will do a move. And then later destroyed at its final destination. Wouldn't that break with interior pointers?

Code like Scoped or ClassCompose is fit for Phobos because it's commonly useful, and because similar questions show that it's far from obvious code.

Perhaps Andrei can offer us some guidance.

Bye,
bearophile
July 13, 2013
On Saturday, 13 July 2013 at 12:47:28 UTC, bearophile wrote:
> In C++ class instances are values, so the class Bar can contain an instance of Foo as a value. This removes one allocation and one indirection and decreases a bit the pressure on the GC. How to do the same thing in D? Can I use Scoped or something to do that?

emplace?
July 13, 2013
Kagamin:

> emplace?

Can you show a little compilable program that solves my problem using emplace?

Bye,
bearophile
« First   ‹ Prev
1 2 3 4