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?

有帮助吗?

解决方案

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

其他提示

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.

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top