www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - GC: memory collected but destructors not called

reply "Tomer Filiba" <tomerfiliba gmail.com> writes:
The following code does not invoke S.~this. If I change `struct 
S` to `class S` - it does. Memory consumption remains constant, 
meaning memory is collected, but destructors are not called.

import std.stdio;
import std.conv;

struct S {
     string s;

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

void main() {
     auto i = 0;
     S[] arr;

     while (true) {
         arr ~= S("hello " ~ text(i++));
         if (arr.length == 1_000_000) {
             writeln(&arr[8888], " = ", arr[8888].s);
             arr.length = 0;
         }
     }
}


Is it a bug? How can I effectively implement RAII with this 
behavior?

The situation is, I allocate resources for my users and return 
them a handle. I can't control what my users do with this handle, 
they might as well append it to a dynamic array/insert to AA, and 
remove it eventually.
Also, the handle might be shared between several logical 
components, so it's not that one of them can explicitly finalize 
it.

Any workaround for this? Perhaps disallow certain types be 
allocated by the GC (I would actually want this feature very 
much)?


-tomer
Nov 10 2014
next sibling parent reply ketmar via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Mon, 10 Nov 2014 08:10:08 +0000
Tomer Filiba via Digitalmars-d <digitalmars-d puremagic.com> wrote:

 The following code does not invoke S.~this. If I change `struct=20
 S` to `class S` - it does. Memory consumption remains constant,=20
 meaning memory is collected, but destructors are not called.
=20
 import std.stdio;
 import std.conv;
=20
 struct S {
      string s;
=20
      ~this() {
          writeln("~S");
      }
 }
=20
 void main() {
      auto i =3D 0;
      S[] arr;
=20
      while (true) {
          arr ~=3D S("hello " ~ text(i++));
          if (arr.length =3D=3D 1_000_000) {
              writeln(&arr[8888], " =3D ", arr[8888].s);
              arr.length =3D 0;
          }
      }
 }
=20
=20
 Is it a bug? How can I effectively implement RAII with this=20
 behavior?
it's a bug, it was recently filled and Walter (afair) made a PR with fix, but it's not yet merged.
 Any workaround for this?
made your own array implementation which manually calls dtors. or wait for next DMD release with this bug fixed. ;-)
Nov 10 2014
parent reply "Tomer Filiba" <tomerfiliba gmail.com> writes:
Thanks for the info, ketmar.

By the way, what do you guys think of adding  nogc to structs, in 
which case they cannot be instantiated by the GC?

e.g.,

 nogc struct S {...}
auto s = new S(); // does not compile
S s;              // all is well


-tomer

On Monday, 10 November 2014 at 08:28:03 UTC, ketmar via 
Digitalmars-d wrote:
 On Mon, 10 Nov 2014 08:10:08 +0000
 Tomer Filiba via Digitalmars-d <digitalmars-d puremagic.com> 
 wrote:

 The following code does not invoke S.~this. If I change 
 `struct S` to `class S` - it does. Memory consumption remains 
 constant, meaning memory is collected, but destructors are not 
 called.
 
 import std.stdio;
 import std.conv;
 
 struct S {
      string s;
 
      ~this() {
          writeln("~S");
      }
 }
 
 void main() {
      auto i = 0;
      S[] arr;
 
      while (true) {
          arr ~= S("hello " ~ text(i++));
          if (arr.length == 1_000_000) {
              writeln(&arr[8888], " = ", arr[8888].s);
              arr.length = 0;
          }
      }
 }
 
 
 Is it a bug? How can I effectively implement RAII with this 
 behavior?
it's a bug, it was recently filled and Walter (afair) made a PR with fix, but it's not yet merged.
 Any workaround for this?
made your own array implementation which manually calls dtors. or wait for next DMD release with this bug fixed. ;-)
Nov 10 2014
parent Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 10 Nov 2014 09:13:09 +0000
schrieb "Tomer Filiba" <tomerfiliba gmail.com>:

 Thanks for the info, ketmar.
 
 By the way, what do you guys think of adding  nogc to structs, in 
 which case they cannot be instantiated by the GC?
 
 e.g.,
 
  nogc struct S {...}
 auto s = new S(); // does not compile
 S s;              // all is well
 
 
 -tomer
That collides with the use of nogc on S to mean that this struct itself wont use the GC, but may be used in a GC context, like as part of a class allocated on the GC heap. Well, in general it means "does not call GC functions". -- Marco
Nov 10 2014
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/10/14 3:10 AM, Tomer Filiba wrote:
 The following code does not invoke S.~this. If I change `struct S` to
 `class S` - it does. Memory consumption remains constant, meaning memory
 is collected, but destructors are not called.

 import std.stdio;
 import std.conv;

 struct S {
      string s;

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

 void main() {
      auto i = 0;
      S[] arr;

      while (true) {
          arr ~= S("hello " ~ text(i++));
          if (arr.length == 1_000_000) {
              writeln(&arr[8888], " = ", arr[8888].s);
              arr.length = 0;
          }
      }
 }


 Is it a bug? How can I effectively implement RAII with this behavior?
Only classes call dtors from the GC. Structs do not. There are many hairy issues with structs calling dtors from GC. Most struct dtors expect to be called synchronously, and are not expecting to deal with multithreading issues. Note that structs inside classes WILL call dtors. For example, imagine you have a struct that increments/decrements a reference count: struct refCounter(T) { T t; // assume this to be a reference type this(T t) { t.inc();} ~this() {if(t !is null) t.dec();} } Of course, you would need more machinery to deal with copies, but let's just assume that is also implemented. You can see how this would be some kind of useful guard using RAII on the stack. Now, imagine you wanted to put this on the GC heap, and the GC would call struct dtors. And let's say the program is multi-threaded. First, the memory referred to by t isn't guaranteed to be alive, it could have already been finalized and freed. Second, the thread that calls the destructor may not be the thread that owns t. This means that two threads could potentially be calling t.inc() or t.dec() at the same time, leading to race conditions. Note, even D's std.stdio.File is not able to deal with this properly.
 The situation is, I allocate resources for my users and return them a
 handle. I can't control what my users do with this handle, they might as
 well append it to a dynamic array/insert to AA, and remove it eventually.
 Also, the handle might be shared between several logical components, so
 it's not that one of them can explicitly finalize it.
Is the resource a GC resource? If so, don't worry about it. If not, use a class to wrap the resource. At this point, that is all that D supports.
 Any workaround for this? Perhaps disallow certain types be allocated by
 the GC (I would actually want this feature very much)?
That would be a nice feature I think. -Steve
Nov 10 2014
next sibling parent reply Rainer Schuetze <r.sagitario gmx.de> writes:
On 10.11.2014 15:19, Steven Schveighoffer wrote:
 Now, imagine you wanted to put this on the GC heap, and the GC would
 call struct dtors. And let's say the program is multi-threaded. First,
 the memory referred to by t isn't guaranteed to be alive, it could have
 already been finalized and freed. Second, the thread that calls the
 destructor may not be the thread that owns t. This means that two
 threads could potentially be calling t.inc() or t.dec() at the same
 time, leading to race conditions.
So, would you object to actually call the destructor for GC collected structs? I don't think that threading problems in the implmentation of the destructor should prohibit this. The reference count in your example also doesn't work for heap allocated structs that are never collected or for structs inside classes. The pull request is almost ready to be merged, please chime in: https://github.com/D-Programming-Language/druntime/pull/864
Nov 11 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/11/14 2:46 PM, Rainer Schuetze wrote:
 On 10.11.2014 15:19, Steven Schveighoffer wrote:
 Now, imagine you wanted to put this on the GC heap, and the GC would
 call struct dtors. And let's say the program is multi-threaded. First,
 the memory referred to by t isn't guaranteed to be alive, it could have
 already been finalized and freed. Second, the thread that calls the
 destructor may not be the thread that owns t. This means that two
 threads could potentially be calling t.inc() or t.dec() at the same
 time, leading to race conditions.
So, would you object to actually call the destructor for GC collected structs? I don't think that threading problems in the implmentation of the destructor should prohibit this. The reference count in your example also doesn't work for heap allocated structs that are never collected or for structs inside classes.
I think in general, it's a problem of the expectation of where structs will live. It's obvious we know classes will be on the heap, so we can write those dtors accordingly. Most struct dtors are written for when the struct is on the stack. The way around this is to have 2 functions for destruction -- as Tango does. One is called during synchronous destruction (i.e. when a struct goes out of scope, or when destroy is called), and the other is called during both synchronous and asynchronous destruction (when the GC is collecting). But even that solution does not allow the struct that I wrote to properly deal with the GC. If the struct has a reference to GC memory, it CANNOT access it during GC destruction to decrement the count, as the memory may be gone. It is why all the claims that we can accomplish what we want with reference counting backed by the GC all never seem to satisfy my doubts.
 The pull request is almost ready to be merged, please chime in:
 https://github.com/D-Programming-Language/druntime/pull/864
At this point, I am not super-concerned about this. I cannot think of any bullet-proof way to ensure that struct dtors for structs that were meant only for stack variables can be called correctly from the GC. This pull doesn't change that, and it does have some nice new features that we do need for other reasons. In other words, putting a struct in the GC heap that was written to be scope-destroyed is an error before and after this pull. Before the pull, the dtor doesn't run, which is wrong, and after the pull the dtor may cause race issues, which is wrong. So either way, it's wrong :) I also am strapped for free cycles to review such PRs. I trust you guys know what you are doing :) -Steve
Nov 11 2014
next sibling parent reply Shachar Shemesh <shachar weka.io> writes:
On 11/11/14 22:41, Steven Schveighoffer wrote:
 At this point, I am not super-concerned about this. I cannot think of
 any bullet-proof way to ensure that struct dtors for structs that were
 meant only for stack variables can be called correctly from the GC.
Isn't "structs meant only for stack variables" a semantic thing? The D compiler cannot possibly know. Shouldn't that be the programmer's choice?
 This
 pull doesn't change that, and it does have some nice new features that
 we do need for other reasons.

 In other words, putting a struct in the GC heap that was written to be
 scope-destroyed is an error before and after this pull. Before the pull,
 the dtor doesn't run, which is wrong, and after the pull the dtor may
 cause race issues, which is wrong. So either way, it's wrong :)
I disagree. The first is wrong. The second is a corner case the programmer needs to be aware of, and account for. The difference is that, in the first case, the programmer is left with no tools to fix the problem, while in the second case this is simply a bug in the program (which, like I said in another email, also happens with the current implementation when the struct is inside a class). In other words, the second case exposes a second (more direct and more likely to be handled) path to an already existing problem, while the first puts the programmer up against a new problem with no work around. Shachar
Nov 11 2014
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/12/14 12:05 AM, Shachar Shemesh wrote:
 On 11/11/14 22:41, Steven Schveighoffer wrote:
 At this point, I am not super-concerned about this. I cannot think of
 any bullet-proof way to ensure that struct dtors for structs that were
 meant only for stack variables can be called correctly from the GC.
Isn't "structs meant only for stack variables" a semantic thing? The D compiler cannot possibly know. Shouldn't that be the programmer's choice?
 This
 pull doesn't change that, and it does have some nice new features that
 we do need for other reasons.

 In other words, putting a struct in the GC heap that was written to be
 scope-destroyed is an error before and after this pull. Before the pull,
 the dtor doesn't run, which is wrong, and after the pull the dtor may
 cause race issues, which is wrong. So either way, it's wrong :)
I disagree. The first is wrong. The second is a corner case the programmer needs to be aware of, and account for.
The programmer being the user of the struct or the designer? It's impossible to force the user to avoid using a struct on the GC, it would be enforcement by comment. But even then, in your dtor, there are issues with accessing what a dtor would normally access. Tell me how you implement reference counting smart pointer when you can't access the reference count in the dtor...
 The difference is that, in the first case,
 the programmer is left with no tools to fix the problem, while in the
 second case this is simply a bug in the program (which, like I said in
 another email, also happens with the current implementation when the
 struct is inside a class).
Sure there are tools, you can wrap the struct in a class if you like pain and suffering. Having the struct dtor called without the wrapper is the same issue.
 In other words, the second case exposes a second (more direct and more
 likely to be handled) path to an already existing problem, while the
 first puts the programmer up against a new problem with no work around.
This means all struct dtors need to be thread aware, and this is not what you want in a language where the type information dictates whether it can be shared or not. -Steve
Nov 12 2014
prev sibling parent reply Rainer Schuetze <r.sagitario gmx.de> writes:
On 11.11.2014 21:41, Steven Schveighoffer wrote:
 In other words, putting a struct in the GC heap that was written to be
 scope-destroyed is an error before and after this pull. Before the pull,
 the dtor doesn't run, which is wrong, and after the pull the dtor may
 cause race issues, which is wrong. So either way, it's wrong :)
I think if someone uses "new" to allocate a struct on the GC heap, he must be aware of the consequences of using the GC. What happens within destruction/finalization is just the same as if it had been wrapped in a class, and that's what everyone(?) expects.
Nov 14 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/14/14 6:25 AM, Rainer Schuetze wrote:
 On 11.11.2014 21:41, Steven Schveighoffer wrote:
 In other words, putting a struct in the GC heap that was written to be
 scope-destroyed is an error before and after this pull. Before the pull,
 the dtor doesn't run, which is wrong, and after the pull the dtor may
 cause race issues, which is wrong. So either way, it's wrong :)
I think if someone uses "new" to allocate a struct on the GC heap, he must be aware of the consequences of using the GC. What happens within destruction/finalization is just the same as if it had been wrapped in a class, and that's what everyone(?) expects.
The issue I have is, how do you enforce that as the author of the struct? Basically, I think you have 2 situations with struct dtors: 1. the struct author intends a struct to strictly be used on the stack (or on some other heap, such as C heap), and never expects to be GC destructed. 2. the struct author allows it to be GC stored, and faces the consequences of that. Even with these 2 options, allowing GC storage precludes certain things. For example, the RefCounted struct goes through great pains to ensure its payload is NOT on the GC heap, because the dtor will not work if the payload is GC based (it may be already deallocated). The issues are so numerous and hairy, that option 2 should really only be left to "experts." I think we need a better story for GC-stored structs than just letting people do it. All this being said, having the GC perform what it is supposed to is also good. Bottom line -- I think the pull is the right thing to do (clearly, not calling dtors in some cases is not correct), but the problems are not all solved. We need to continue working on this until it's quite simple to make a struct that behaves correctly no matter where you put it. -Steve
Nov 14 2014
parent reply Rainer Schuetze <r.sagitario gmx.de> writes:
On 14.11.2014 16:44, Steven Schveighoffer wrote:
 On 11/14/14 6:25 AM, Rainer Schuetze wrote:
 On 11.11.2014 21:41, Steven Schveighoffer wrote:
 In other words, putting a struct in the GC heap that was written to be
 scope-destroyed is an error before and after this pull. Before the pull,
 the dtor doesn't run, which is wrong, and after the pull the dtor may
 cause race issues, which is wrong. So either way, it's wrong :)
I think if someone uses "new" to allocate a struct on the GC heap, he must be aware of the consequences of using the GC. What happens within destruction/finalization is just the same as if it had been wrapped in a class, and that's what everyone(?) expects.
The issue I have is, how do you enforce that as the author of the struct? Basically, I think you have 2 situations with struct dtors: 1. the struct author intends a struct to strictly be used on the stack (or on some other heap, such as C heap), and never expects to be GC destructed.
You can define "new" in the struct: struct S { new(size_t sz); int x; } This causes a link error with "new S;" Unfortunately disable has no effect (bug?). You can also generate a runtime error within "new".
 2. the struct author allows it to be GC stored, and faces the
 consequences of that.
 Even with these 2 options, allowing GC storage precludes certain things.
 For example, the RefCounted struct goes through great pains to ensure
 its payload is NOT on the GC heap, because the dtor will not work if the
 payload is GC based (it may be already deallocated). The issues are so
 numerous and hairy, that option 2 should really only be left to "experts."

 I think we need a better story for GC-stored structs than just letting
 people do it.
I understand there are some complications involved, especially with stuff like RefCounted. With precise scanning, we might be able to sort finalization according to (likely) dependencies, but this will not be 100% safe and slow down collection even more. There are so many ways people can do something silly/wrong, I don't think we can foresee everything. In addition to allocating it on the heap, the struct could be copied to some global, be kept forever in an associative array, be part of a class or just memset with zeros.
 All this being said, having the GC perform what it is supposed to is
 also good. Bottom line -- I think the pull is the right thing to do
 (clearly, not calling dtors in some cases is not correct), but the
 problems are not all solved. We need to continue working on this until
 it's quite simple to make a struct that behaves correctly no matter
 where you put it.
Yeah, even if not fulfilling all desires it is progress in the right direction ;-)
Nov 14 2014
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/14/14 11:55 AM, Rainer Schuetze wrote:
 On 14.11.2014 16:44, Steven Schveighoffer wrote:
 The issue I have is, how do you enforce that as the author of the struct?
You can define "new" in the struct: struct S { new(size_t sz); int x; }
This doesn't stop array allocation, or allocation as part of a class. -Steve
Nov 14 2014
prev sibling next sibling parent reply Shachar Shemesh <shachar weka.io> writes:
On 10/11/14 16:19, Steven Schveighoffer wrote:
 Only classes call dtors from the GC. Structs do not. There are many
 hairy issues with structs calling dtors from GC. Most struct dtors
 expect to be called synchronously, and are not expecting to deal with
 multithreading issues.

 Note that structs inside classes WILL call dtors.
How is this any different? If one should not be allowed, how is the other okay? Shachar
Nov 11 2014
next sibling parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 12 November 2014 at 04:59:33 UTC, Shachar Shemesh 
wrote:
 On 10/11/14 16:19, Steven Schveighoffer wrote:
 Only classes call dtors from the GC. Structs do not. There are 
 many
 hairy issues with structs calling dtors from GC. Most struct 
 dtors
 expect to be called synchronously, and are not expecting to 
 deal with
 multithreading issues.

 Note that structs inside classes WILL call dtors.
How is this any different? If one should not be allowed, how is the other okay?
Supposedly, a struct destructor will only access resources that the struct itself manages. As long as that's the case, it will be safe. In practice, there's still a lot that can go wrong.
Nov 12 2014
parent reply Shachar Shemesh <shachar weka.io> writes:
On 12/11/14 11:29, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net>" wrote:

 Supposedly, a struct destructor will only access resources that the
 struct itself manages. As long as that's the case, it will be safe. In
 practice, there's still a lot that can go wrong.
Either a struct's destructor can be run from the context of a GC, in which case it should run when the struct is directly allocated on the heap, or it is not, in which case the fact it is run when the struct is inside a class should be considered a bug. Today it happens for structs nested in classes, but not allocated directly. I don't see any situation in which this is not a bug. Shachar
Nov 12 2014
parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 12 November 2014 at 13:56:08 UTC, Shachar Shemesh 
wrote:
 On 12/11/14 11:29, "Marc =?UTF-8?B?U2Now7x0eiI=?= 
 <schuetzm gmx.net>" wrote:

 Supposedly, a struct destructor will only access resources 
 that the
 struct itself manages. As long as that's the case, it will be 
 safe. In
 practice, there's still a lot that can go wrong.
Either a struct's destructor can be run from the context of a GC, in which case it should run when the struct is directly allocated on the heap, or it is not, in which case the fact it is run when the struct is inside a class should be considered a bug. Today it happens for structs nested in classes, but not allocated directly. I don't see any situation in which this is not a bug. Shachar
I think it's helpful to ask the question who's responsible for destroying an object. If it's the GC, then it's finalization, if it's no the GC, it's destruction. Both a destructor and a finalizer need to clean up themselves, and any other object they own. This includes embedded structs, but not GC managed objects created by the constructor. This applies to both structs and classes as the owning objects, and manual/automatic management as well as GC. But indeed, what's implemented today is inconsistent.
Nov 12 2014
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/11/14 11:59 PM, Shachar Shemesh wrote:
 On 10/11/14 16:19, Steven Schveighoffer wrote:
 Only classes call dtors from the GC. Structs do not. There are many
 hairy issues with structs calling dtors from GC. Most struct dtors
 expect to be called synchronously, and are not expecting to deal with
 multithreading issues.

 Note that structs inside classes WILL call dtors.
How is this any different? If one should not be allowed, how is the other okay?
I'm not defending the status quo, I'm just saying what happens today. But adding struct dtor calls to the GC will not solve the problems identified here. -Steve
Nov 12 2014
parent reply "Uranuz" <neuranuz gmail.com> writes:
If we will have something like *scoped destructor* (that will be 
executed at scope exit) could it help to release some resources? 
I worry about this problem too because even using class to hold 
resource I expirience some *delays* in relesing them. For example 
I have database connection opened. And I want to close it when I 
finished my job. Relying on GC I sometimes experiece problems 
like *too many DB connections*, because GC frees it not enough 
quickly.
Nov 12 2014
next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Wednesday, 12 November 2014 at 20:00:31 UTC, Uranuz wrote:
 If we will have something like *scoped destructor* (that will 
 be executed at scope exit) could it help to release some 
 resources?
Don't know what you mean here. Isn't that just a normal destructor?
 I worry about this problem too because even using class to hold 
 resource I expirience some *delays* in relesing them. For 
 example I have database connection opened. And I want to close 
 it when I finished my job. Relying on GC I sometimes experiece 
 problems like *too many DB connections*, because GC frees it 
 not enough quickly.
I'd say database connections and file descriptors simply shouldn't be managed by the GC. It is good at managing memory, but not for other things. It's better to have a connection pool, and from that pool take a reference to the DB connection, use it as long as you need it, and then give it back. This can be implemented nicely with scope(exit).
Nov 12 2014
prev sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/12/14 3:00 PM, Uranuz wrote:
 If we will have something like *scoped destructor* (that will be
 executed at scope exit) could it help to release some resources? I worry
 about this problem too because even using class to hold resource I
 expirience some *delays* in relesing them. For example I have database
 connection opened. And I want to close it when I finished my job.
 Relying on GC I sometimes experiece problems like *too many DB
 connections*, because GC frees it not enough quickly.
GC I would use as a last resort. Let's say you leak the object, forget to dispose it. Do you want it to leak the DB resource too? Basically, you want a close() or dispose() method on your object, then you can let the GC clean the memory, and synchronously close the DB connection. -Steve
Nov 12 2014
prev sibling parent "eles" <eles215 gzk.dot> writes:
On Monday, 10 November 2014 at 14:19:26 UTC, Steven Schveighoffer 
wrote:

 Is the resource a GC resource? If so, don't worry about it.
I might be wrong but my view is that in presence of a GC and undre the abstraction of R Chen, it is wrong to think about memory as being a resource anymore. If the abstraction is that of a machine with infinite memory, then the very mechanism of freeing memory shall be abstracted for. This asks for decoupling the memory management of the management of the other resources and, in particular, RAII-like management. The management of memory and any other resources is pretty much identical under C++ since they share the same paradigm. In GC languages, this is no longer the case. So, the real question (that will also cover destructors throwing exceptions and so on) is not what is allowed inside a destructor (aka finalizer) but *what is allowed in a constructor* of a GC-managed object. Since for symmetry purposes the destructor should undo what the constructor did, this cuts the problem as follows: 1) in GC-entities, the destructor should not deal with releasing memory. Not only this job is no longer his, but *there is no need for this kind of job*. Memory is infinite. 2) from symmetry, the constructor should not allocate memory as it would care about its lifetime. 3) the question in that case is how the other resources are managed? 3a) If they are to be released in the destructor, then the destructor's job should be only this (no memory dealloc). In this case, the resources should be taken in the constructor. 3b) if the destructor disappears completely, then the place to acquire resources is no longer in the constructor anymore, since those will never be released. Ideally, a transparent mechanism to allocate memory would be needed, without explicit allocation. In this case, the symmetry would be conserved: the constructor will only acqire resources (but not memory!) and those resources are released, symmetrically, in the destructor, at the end of the lifetime.
Nov 12 2014