Jump to page: 1 2
Thread overview
Odd Destructor Behavior
Feb 07, 2016
Matt Elkins
Feb 07, 2016
anonymous
Feb 07, 2016
Matt Elkins
Feb 07, 2016
anonymous
Feb 07, 2016
Matt Elkins
Feb 07, 2016
Matt Elkins
Feb 08, 2016
Daniel Kozak
Feb 09, 2016
Matt Elkins
Feb 07, 2016
Márcio Martins
Feb 07, 2016
anonymous
Feb 07, 2016
Matt Elkins
February 07, 2016
I've been experiencing some odd behavior, where it would appear that a struct's destructor is being called before the object's lifetime expires. More likely I am misunderstanding something about the lifetime rules for structs. I haven't been able to reproduce with a particularly minimal example, so I will try to explain with my current code:

I have a struct called "TileView", with the relevant parts looking like so:
[code]
struct TileView
{
    this(Texture.Handle wallsTexture, Texture.Handle topTexture)
    {
        // Work happens here, but it doesn't seem to matter to reproducing the condition
    }

    // Destructor added for debugging after seeing odd behavior
    ~this()
    {
        import std.stdio;
        writeln("HERE2");
    }
    // ...more implementation that doesn't seem to affect the condition...
}
[/code]

An instance of this is stored in another struct called "View", with the relevant parts looking like so:
[code]
struct View
{
    this(/* irrelevant args here */)
    {
        writeln("HERE1a");
        m_tileView = TileView(Texture.create(loadTGA(makeInputStream!FileInputStream("resources/images/grass-topped-clay.tga").handle)), Texture.create(loadTGA(makeInputStream!FileInputStream("resources/images/grass-outlined.tga").handle)));//, Texture.create(loadTGA(makeInputStream!FileInputStream("resources/images/grass-outlined.tga").handle)));
        writeln("HERE1b");
    }

    TileView m_tileView;
    // ...more irrelevant implementation...
}
[/code]

The output from the two writelns in View and the one in TileView is:
[output]
HERE1a
HERE2
HERE1b
[/output]

So the destructor of TileView is being called during its construction. Flow proceeds normally (e.g., no exception is thrown), as demonstrated by "HERE1b" being printed. Interestingly enough, it all seems to hinge on the second argument to TileView's constructor; if I make it on a separate line beforehand and pass it in, or if I don't pass in a second argument at all, I don't see this behavior. In fact, almost any attempt I've made to reduce the problem for illustration causes it to vanish, which is unfortunate.

From this non-reduced situation, does anything jump out? Am I missing something about struct lifetimes? This is the only place I instantiate a TileView.

Thanks!


February 07, 2016
On 07.02.2016 22:49, Matt Elkins wrote:
>  From this non-reduced situation, does anything jump out? Am I missing
> something about struct lifetimes? This is the only place I instantiate a
> TileView.

Looks weird. I presume this doesn't happen with simpler constructor parameters/arguments, like int instead of Texture.Handle? I don't see how the parameter types would make a destructor call appear. Might be a bug.

Can you post the code for Texture, makeInputStream, etc, so that we have a full, reproducible test case?
February 07, 2016
On Sunday, 7 February 2016 at 21:49:24 UTC, Matt Elkins wrote:
> I've been experiencing some odd behavior, where it would appear that a struct's destructor is being called before the object's lifetime expires. More likely I am misunderstanding something about the lifetime rules for structs. I haven't been able to reproduce with a particularly minimal example, so I will try to explain with my current code:
>
> [...]

The destructor you are seeing is from the assignment:

m_tileView = TileView(...);

This creates a temporary TileView, copies it to m_tileView, and then destroys it. I suppose you want to move it instead. You need to copy the handles from the temporary into the destination, and then clear them out from the temporary to prevent them from being released.

std.algorithm has a couple of move() overloads that might be useful here.
February 07, 2016
On 07.02.2016 23:07, Márcio Martins wrote:
> The destructor you are seeing is from the assignment:
>
> m_tileView = TileView(...);
>
> This creates a temporary TileView, copies it to m_tileView, and then
> destroys it. I suppose you want to move it instead. You need to copy the
> handles from the temporary into the destination, and then clear them out
> from the temporary to prevent them from being released.

I think you're mistaken here. The result of a struct literal is usually moved implicitly.

Code:
----
import std.stdio;

struct S
{
    ~this() {writeln("dtor");}
}

void main()
{
    auto s = S();
    writeln("end of main");
}
----

Output:
----
end of main
dtor
----

If there was a copy that's destroyed after the assignment, there should be another "dtor" before "end of main".
February 07, 2016
On Sunday, 7 February 2016 at 22:35:57 UTC, anonymous wrote:
> On 07.02.2016 23:07, Márcio Martins wrote:
>> The destructor you are seeing is from the assignment:
>>
>> m_tileView = TileView(...);
>>
>> This creates a temporary TileView, copies it to m_tileView, and then
>> destroys it. I suppose you want to move it instead. You need to copy the
>> handles from the temporary into the destination, and then clear them out
>> from the temporary to prevent them from being released.
>
> I think you're mistaken here. The result of a struct literal is usually moved implicitly.
>
> Code:
> ----
> import std.stdio;
>
> struct S
> {
>     ~this() {writeln("dtor");}
> }
>
> void main()
> {
>     auto s = S();
>     writeln("end of main");
> }
> ----
>
> Output:
> ----
> end of main
> dtor
> ----
>
> If there was a copy that's destroyed after the assignment, there should be another "dtor" before "end of main".

Yeah...and I just stuck this into TileView:
@disable this();
@disable this(this);

and it compiled just fine. If it created a copy I assume the compiler would have choked on that.
February 07, 2016
On Sunday, 7 February 2016 at 22:04:27 UTC, anonymous wrote:
> On 07.02.2016 22:49, Matt Elkins wrote:
>>  From this non-reduced situation, does anything jump out? Am I missing
>> something about struct lifetimes? This is the only place I instantiate a
>> TileView.
>
> Looks weird. I presume this doesn't happen with simpler constructor parameters/arguments, like int instead of Texture.Handle? I don't see how the parameter types would make a destructor call appear. Might be a bug.

Correct; if I switch the second Texture.Handle to an int it doesn't happen. Nor if I remove it altogether. Nor if I create the Texture.Handle on the line immediately above TileView's construction, and then pass in the created Texture.Handle. I also didn't understand how the parameters would cause this.

> Can you post the code for Texture, makeInputStream, etc, so that we have a full, reproducible test case?

Oi. Yes, I can, but it is quite a lot of code even if you don't count that it is dependent on OpenGL, GLFW, and gl3n to run to this point. This is why I was disappointed that simpler reproducing cases weren't appearing. I should probably spend more time trying to reduce the case some...
February 08, 2016
On 07.02.2016 23:49, Matt Elkins wrote:
> Oi. Yes, I can, but it is quite a lot of code even if you don't count
> that it is dependent on OpenGL, GLFW, and gl3n to run to this point.
> This is why I was disappointed that simpler reproducing cases weren't
> appearing. I should probably spend more time trying to reduce the case
> some...

Minimal test cases are great, but if you're not able to get it down in size, or not willing to, then a larger test case is ok, too. The problem is clear, and I'd expect reducing it to be relatively straight-foward (but possibly time-consuming). Just don't forget about it completely, that would be bad.

Also be aware of DustMite, a tool for automatic reduction:

https://github.com/CyberShadow/DustMite
February 07, 2016
On Sunday, 7 February 2016 at 23:11:34 UTC, anonymous wrote:
> On 07.02.2016 23:49, Matt Elkins wrote:
>> Oi. Yes, I can, but it is quite a lot of code even if you don't count
>> that it is dependent on OpenGL, GLFW, and gl3n to run to this point.
>> This is why I was disappointed that simpler reproducing cases weren't
>> appearing. I should probably spend more time trying to reduce the case
>> some...
>
> Minimal test cases are great, but if you're not able to get it down in size, or not willing to, then a larger test case is ok, too. The problem is clear, and I'd expect reducing it to be relatively straight-foward (but possibly time-consuming). Just don't forget about it completely, that would be bad.
>
> Also be aware of DustMite, a tool for automatic reduction:
>
> https://github.com/CyberShadow/DustMite

Turns out it was less hard to reduce than I thought. Maybe it could be taken down some more, too, but this is reasonably small:

[code]
import std.stdio;

struct TextureHandle
{
    ~this() {}
}

TextureHandle create() {return TextureHandle();}

 struct TileView
 {
     @disable this();
     @disable this(this);
     this(TextureHandle a, TextureHandle b) {}
     ~this() {writeln("HERE2");}
 }

 struct View
 {
     this(int)
     {
         writeln("HERE1a");
         m_tileView = TileView(create(), create());
         writeln("HERE1b");
     }

     private TileView m_tileView;
}

unittest
{
    auto v = View(5);
}
[/code]

This yields the following:

[output]
HERE1a
HERE2
HERE1b
HERE2
[/output]

I would have expected only one "HERE2", the last one. Any of a number of changes cause it to behave in the expected way, including (but probably not limited to):
* Creating the TextureHandles directly rather than calling create()
* Using only one argument to TileView's constructor
* Removing TextureHandle's empty destructor

That last one especially seems to indicate a bug to me...
February 07, 2016
Some environment information:
DMD 2.070 32-bit
Windows 7 (64-bit)
February 08, 2016
V Sun, 07 Feb 2016 23:47:39 +0000
Matt Elkins via Digitalmars-d-learn <digitalmars-d-learn@puremagic.com>
napsáno:

> On Sunday, 7 February 2016 at 23:11:34 UTC, anonymous wrote:
> > On 07.02.2016 23:49, Matt Elkins wrote:
> >> Oi. Yes, I can, but it is quite a lot of code even if you
> >> don't count
> >> that it is dependent on OpenGL, GLFW, and gl3n to run to this
> >> point.
> >> This is why I was disappointed that simpler reproducing cases
> >> weren't
> >> appearing. I should probably spend more time trying to reduce
> >> the case
> >> some...
> >
> > Minimal test cases are great, but if you're not able to get it down in size, or not willing to, then a larger test case is ok, too. The problem is clear, and I'd expect reducing it to be relatively straight-foward (but possibly time-consuming). Just don't forget about it completely, that would be bad.
> >
> > Also be aware of DustMite, a tool for automatic reduction:
> >
> > https://github.com/CyberShadow/DustMite
> 
> Turns out it was less hard to reduce than I thought. Maybe it could be taken down some more, too, but this is reasonably small:
> 
> [code]
> import std.stdio;
> 
> struct TextureHandle
> {
>      ~this() {}
> }
> 
> TextureHandle create() {return TextureHandle();}
> 
>   struct TileView
>   {
>       @disable this();
>       @disable this(this);
>       this(TextureHandle a, TextureHandle b) {}
>       ~this() {writeln("HERE2");}
>   }
> 
>   struct View
>   {
>       this(int)
>       {
>           writeln("HERE1a");
>           m_tileView = TileView(create(), create());
>           writeln("HERE1b");
>       }
> 
>       private TileView m_tileView;
> }
> 
> unittest
> {
>      auto v = View(5);
> }
> [/code]
> 
> This yields the following:
> 
> [output]
> HERE1a
> HERE2
> HERE1b
> HERE2
> [/output]
> 
> I would have expected only one "HERE2", the last one. Any of a
> number of changes cause it to behave in the expected way,
> including (but probably not limited to):
> * Creating the TextureHandles directly rather than calling
> create()
> * Using only one argument to TileView's constructor
> * Removing TextureHandle's empty destructor
> 
> That last one especially seems to indicate a bug to me...
Seems to me too, please report it on issues.dlang.org

« First   ‹ Prev
1 2