5

In D, it's possible to allocate classes on the stack using scope, i.e.

void foo()
{
    scope example = new Bar();
}

Now, if class Foo has class Bar as a member, is there any way to store Bar in-place inside Foo and have it tear down with Foo a la C++?

I had hoped that

import std.stdio;

class Foo {
    this() { writeln("Foo"); }

    ~this() { writeln("~Foo"); }

    scope Bar inside;
}

class Bar {

    this() { writeln("Bar"); }

    ~this() { writeln("~Bar"); }
};

void main()
{
    scope f = new Foo();
    writeln("I'm a main!");
}

would yield something like

Bar
Foo
I'm a main!
~Bar
~Foo

Instead I only get

Foo
I'm a main!
~Foo

It seems like storing a class member as a garbage-collected reference instead of in-place is just needlessly complicating the heap layout for little benefit. And what is scope doing in this case, if not specifying to hold Bar in-place?

Matt Kline
  • 10,149
  • 7
  • 50
  • 87

3 Answers3

5

Don't use scope classes. Those are effectively deprecated and remain only as legacy of old days. There is a Phobos helper that achieves similar effect. Your example re-written:

import std.stdio;
import std.typecons;

alias ScopedBar = typeof(scoped!Bar());

class Bar {

    this() { writeln("Bar"); }

    ~this() { writeln("~Bar"); }
};

class Foo
{   
    this()
    {
        this.inside = scoped!Bar();
        writeln("Foo");
    }

    ~this() { writeln("~Foo"); }

    ScopedBar inside;
}

void main()
{
    writeln("Main before scope");
    {
        auto f = scoped!Foo();
    }
    writeln("Main after scope");
}
Mihails Strasuns
  • 3,783
  • 1
  • 18
  • 21
2

If Bar is a struct, you'll get what you want, without needing scope (however, structs cannot have default constructors, to the writeln("Bar") won't be permitted). Unless you need interfaces and virtual functions, I think structs are generally the better tool to use in D since they are more flexible. You can very easily have a scope struct with deterministic destruction, no indirection, etc. If you want it to be a reference, you can always use a pointer to it or a pointer to a private internal implementation to force reference semantics.

It is possible to wrap a class in a struct, including in-place allocation:

import std.stdio;

// bar is first because of https://d.puremagic.com/issues/show_bug.cgi?id=12278
class Bar {
    this() { writeln("Bar"); }
    ~this() { writeln("~Bar"); }
}


class Foo {
    this() {
        writeln("Foo");
        // since there's no default ctor for structs, we force
        // initialization right here
        inside = InPlace!Bar.getDefault();
    }

    ~this() { writeln("~Foo"); }

    // InPlace instead of scope...
    InPlace!Bar inside;
}

struct InPlace(T) {
    // an in-place buffer for the class data...
    private byte[__traits(classInstanceSize, T)] rawData;

    @property T obj() { return cast(T) rawData.ptr; }
    alias obj this; // DANGER: don't escape this reference!
    @disable this(); // force initialization at the usage site

    // get it default-constructed
    static InPlace!T getDefault() {
        InPlace!T t = void;
        t.initializeObject();
        t.obj.__ctor();
        return t;
    }

    void initializeObject() {
        assert(__traits(classInstanceSize, T) == 8);
        assert(T.classinfo.init.length == 8);
        assert(this.rawData.length == 8);
        this.rawData[] = T.classinfo.init[];
    }

    // ctors with args
    this(T...)(T t) {
        initializeObject();
        obj._ctor(t);
    }
    ~this() {
        .destroy(obj); // call the class destructor in the struct dtor
    }
}

void main()
{
    scope f = new Foo();
    writeln("I'm a main!");
}

edit: std.typecons.scoped does this too, I wasn't sure it would work in the class member but Михаил Страшун's answer showed it indeed does. /edit

That will do the trick... but I recommend against it. You should just make Bar itself a struct if you can. The danger with this wrapper magic is if you escape the reference to the inner object you are liable to get random crashes if the outer one is destroyed first. It might be ok if it is private though, I guess, though there is still some danger there (private in D is module private, so another function in that module might still escape it, but still, if you are careful enough you can be ok with it.)

Running gives: Foo Bar I'm a main! ~Foo ~Bar

BTW the scope thing you use in the OP is currently deprecated because it isn't implemented safely/completely. I don't think it even stack allocates right now, it is more like automatically inserting scope(exit) .destroy(foo); right after the declaration.

Adam D. Ruppe
  • 25,382
  • 4
  • 41
  • 60
  • I wonder why Scoped!T struct itself is hidden though (resulting in that alias workaround forced) – Mihails Strasuns Feb 28 '14 at 21:28
  • My guess is to discourage its usage outside function-local scope (or just voldemort types being overused; I think it used to be publicly available - I seem to remember using Scoped!T before) – Adam D. Ruppe Mar 01 '14 at 02:11
  • DPaste is getting an error compiling this code: http://dpaste.dzfl.pl/d659656fac86 – DejanLekic Mar 01 '14 at 11:40
  • just remove those asserts and it will work, I put them in to chase down the compiler bug. On 64 bit, the class size is 16 bytes instead of the 8 I assumed there. – Adam D. Ruppe Mar 01 '14 at 13:59
0

You do not get Bar and ~Bar because you never made an instance of that class.

DejanLekic
  • 18,787
  • 4
  • 46
  • 77
  • That seems to go without saying. I had hoped that `scope Bar inside;` would have that effect, though it just seems to create a reference to `Bar` a a member of `Foo`. What does `static` do in that context? Is there a method to do what I'm trying to accomplish? – Matt Kline Feb 28 '14 at 17:38