www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Odd Destructor Behavior

reply Matt Elkins <notreal fake.com> writes:
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-
lay.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!
Feb 07 2016
next sibling parent reply anonymous <anonymous example.com> writes:
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?
Feb 07 2016
parent reply Matt Elkins <notreal fake.com> writes:
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...
Feb 07 2016
parent reply anonymous <anonymous example.com> writes:
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
Feb 07 2016
parent reply Matt Elkins <notreal fake.com> writes:
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...
Feb 07 2016
next sibling parent Matt Elkins <notreal fake.com> writes:
Some environment information:
DMD 2.070 32-bit
Windows 7 (64-bit)
Feb 07 2016
prev sibling parent reply Daniel Kozak via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
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
Feb 07 2016
parent Matt Elkins <notreal fake.com> writes:
On Monday, 8 February 2016 at 07:31:07 UTC, Daniel Kozak wrote:
 Seems to me too, please report it on issues.dlang.org
Reported: https://issues.dlang.org/show_bug.cgi?id=15661
Feb 08 2016
prev sibling parent reply =?UTF-8?B?TcOhcmNpbw==?= Martins <marcioapm gmail.com> writes:
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.
Feb 07 2016
parent reply anonymous <anonymous example.com> writes:
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".
Feb 07 2016
parent Matt Elkins <notreal fake.com> writes:
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.
Feb 07 2016