Re: Odd Destructor Behavior
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
Re: Odd Destructor Behavior
V Sun, 07 Feb 2016 23:47:39 + Matt Elkins via Digitalmars-d-learnnapsá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
Re: Odd Destructor Behavior
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.
Odd Destructor Behavior
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!
Re: Odd Destructor Behavior
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.
Re: Odd Destructor Behavior
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?
Re: Odd Destructor Behavior
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".
Re: Odd Destructor Behavior
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...
Re: Odd Destructor Behavior
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
Re: Odd Destructor Behavior
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...
Re: Odd Destructor Behavior
Some environment information: DMD 2.070 32-bit Windows 7 (64-bit)