digitalmars.D.learn - Odd Destructor Behavior
- Matt Elkins (62/62) Feb 07 2016 I've been experiencing some odd behavior, where it would appear
- anonymous (6/9) Feb 07 2016 Looks weird. I presume this doesn't happen with simpler constructor
- Matt Elkins (11/23) Feb 07 2016 Correct; if I switch the second Texture.Handle to an int it
- anonymous (8/13) Feb 07 2016 Minimal test cases are great, but if you're not able to get it down in
- Matt Elkins (47/64) Feb 07 2016 Turns out it was less hard to reduce than I thought. Maybe it
- Matt Elkins (3/3) Feb 07 2016 Some environment information:
- Daniel Kozak via Digitalmars-d-learn (4/83) Feb 07 2016 V Sun, 07 Feb 2016 23:47:39 +0000
- Matt Elkins (2/3) Feb 08 2016 Reported: https://issues.dlang.org/show_bug.cgi?id=15661
- =?UTF-8?B?TcOhcmNpbw==?= Martins (10/17) Feb 07 2016 The destructor you are seeing is from the assignment:
- anonymous (23/29) Feb 07 2016 I think you're mistaken here. The result of a struct literal is usually
- Matt Elkins (6/40) Feb 07 2016 Yeah...and I just stuck this into TileView:
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
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
On Sunday, 7 February 2016 at 22:04:27 UTC, anonymous wrote:On 07.02.2016 22:49, Matt Elkins wrote: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.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?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
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
On Sunday, 7 February 2016 at 23:11:34 UTC, anonymous wrote:On 07.02.2016 23:49, Matt Elkins wrote: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...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
Some environment information: DMD 2.070 32-bit Windows 7 (64-bit)
Feb 07 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:Seems to me too, please report it on issues.dlang.orgOn 07.02.2016 23:49, Matt Elkins wrote: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...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
On Monday, 8 February 2016 at 07:31:07 UTC, Daniel Kozak wrote:Seems to me too, please report it on issues.dlang.orgReported: https://issues.dlang.org/show_bug.cgi?id=15661
Feb 08 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.
Feb 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".
Feb 07 2016
On Sunday, 7 February 2016 at 22:35:57 UTC, anonymous wrote:On 07.02.2016 23:07, Márcio Martins wrote: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.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