Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In D, is it possible for an object to hold a member object internally?

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?

like image 617
Matt Kline Avatar asked Feb 28 '14 17:02

Matt Kline


People also ask

Can you have an object within an object?

A JavaScript Object is a collection of Key-Value pairs, and nested objects are objects that have other objects inside them as their property. Nesting is a widely used practice in programming as it provides the code with more enhanced details.

Is it possible that an object of is passed to a function also have an object of same name?

14. Is it possible that an object of is passed to a function, and the function also have an object of same name? Explanation: There can't be more than one variable or object with the same name in same scope.

Where can an object store its states?

An object stores its state in fields (variables in some programming languages) and exposes its behavior through methods (functions in some programming languages). Methods operate on an object's internal state and serve as the primary mechanism for object-to-object communication.

Can you create an object inside a method?

In Java, we can create Objects in various ways: Using a new keyword. Using the newInstance () method of the Class class. Using the newInstance() method of the Constructor class.


2 Answers

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");
}
like image 148
Mihails Strasuns Avatar answered Nov 15 '22 09:11

Mihails Strasuns


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.

like image 38
Adam D. Ruppe Avatar answered Nov 15 '22 07:11

Adam D. Ruppe