www.digitalmars.com         C & C++   DMDScript  

c++ - When destructors are called

reply DQNOK <davidlqualls yahoo.com> writes:
I though destructors HAD TO BE CALLED when an object went out of
scope.  I realize that as long as the object is in scope, when the
destructor is called is implementation dependent.  (Well, I
suppose that is still standard C++ behavior).

I have code that really needs the destructor called when I want it
called.  I've tried

{ // create a new scope here
   printf( "%s", (char*)mytype(&x) ); //creates a mytype temporary
} // end the scope here to force mytype::~mytype on the temporary
//shouldn't be necessary because the temporary is out of scope
//immediately after the printf call.

but it's not working.  The destructor is not being called until
the end of the function.  The mytype constructor alters x (inserts
a null terminator for use with Standard C library functions), then
the destructor puts it back to the way it was (removes the null
terminator).  Since the destructor is not be called immediately, x
is being left altered and unuseable until after the function exits.

Visual C++ calls the destructor immediately, and all is good.
Digital Mars C++ waits to call the destructor and all is not good.

Is this conforming behavior?  Is there a way to force a destructor
call?

Thanks
David
Oct 03 2007
next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
If you could boil this down to a small test case, I'd appreciate it.
Oct 09 2007
prev sibling parent reply Heinz Saathoff <newshsaat arcor.de> writes:
DQNOK wrote...
 I have code that really needs the destructor called when I want it
 called.  I've tried
 
 { // create a new scope here
    printf( "%s", (char*)mytype(&x) ); //creates a mytype temporary
 } // end the scope here to force mytype::~mytype on the temporary
 //shouldn't be necessary because the temporary is out of scope
 //immediately after the printf call.
I'm not sure if the code is valid. You create a temporary 'mytype' and cast this object to a char*. Note that constructors don't return a value. I tried a small example with the cast an got this message: printf("my_type modified: %s\n", (char*)mytype(my_str)); ^ a.cpp(19) : Error: illegal cast from: mytype to : char *
 but it's not working.  The destructor is not being called until
 the end of the function.
Without the cast this works (but is still not portable): #include <stdio.h> struct mytype { mytype(char *s) : str(s) { prev_char = str[0]; str[0] = 'A'; } ~mytype() {str[0] = prev_char;} char *str; char prev_char; };//mytype int main() { char my_str[] = " new way to do it"; printf("my_type modified: %s\n", mytype(my_str)); printf("old again: %s\n", my_str); return 0; } This example relies on object layout and is therefore not portable. - Heinz
Oct 16 2007
next sibling parent reply DQNOK <davidlqualls yahoo.com> writes:
== Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 DQNOK wrote...
 I have code that really needs the destructor called when I
want it
 called.  I've tried

 { // create a new scope here
    printf( "%s", (char*)mytype(&x) ); //creates a mytype
temporary
 } // end the scope here to force mytype::~mytype on the
temporary
 //shouldn't be necessary because the temporary is out of scope
 //immediately after the printf call.
I'm not sure if the code is valid.
It compiles and runs under both DMC, and VC, without any warnings. Under VC (using the debugger to track thru it) the destructor on the temporary is called immediately when it goes out of scope (right after printf is through with it). Under DMC, the temporary's destructor is not called until the end of the (main()) function.
You create a temporary 'mytype' and
 cast this object to a char*.
I have overloaded the operator char*() and operator const char*() to return the correct character pointer member of mytype. It works fine.
 This example relies on object layout and is therefore not
portable.
 - Heinz
I'm not relying on object layout; I specify the "cptr" member when casting. Apparently it's still not portable anyway because I'm relying on the destructor to be called in a deterministic way. Thank you Heinz for your thoughts and response.
Oct 16 2007
parent reply Heinz Saathoff <newshsaat arcor.de> writes:
Hello,


DQNOK wrote...
 
You create a temporary 'mytype' and
 cast this object to a char*.
I have overloaded the operator char*() and operator const char*() to return the correct character pointer member of mytype. It works fine.
Ok, didn't know this.
 This example relies on object layout and is therefore not
portable.
 - Heinz
I'm not relying on object layout; I specify the "cptr" member when casting. Apparently it's still not portable anyway because I'm relying on the destructor to be called in a deterministic way.
As far as I know the actual standard is more precise in when the destructor must be called. I've added the operator char*() to may program and it runs with dmc (scppn version is 8.48.10n). This is the test prog: #include <stdio.h> struct mytype { mytype(char *s) : str(s) { prev_char = str[0]; str[0] = 'A'; } ~mytype() {str[0] = prev_char;} operator char* () { return str; } char prev_char; char *str; };//mytype int main() { char my_str[] = " new way to do it"; printf("my_type modified: %s\n", (char*)mytype(my_str)); printf("old again: %s\n", my_str); return 0; }
 Thank you Heinz for your thoughts and response.
- Heinz
Oct 17 2007
parent reply DQNOK <davidlqualls yahoo.com> writes:
 As far as I know the actual standard is more precise in when the
 destructor must be called.
I don't know what the standard says. Where can I find it (short of purchasing it from ISO or ANSI).
 I've added the operator char*() to may program and it runs with
 dmc (scppn version is 8.48.10n).
Great! Looks like maybe I just need to upgrade. I'm still running 8.42. Thanks again, Heinz. David
Oct 17 2007
parent reply DQNOK <davidlqualls gmail.com> writes:
 I've added the operator char*() to may program and it runs with
 dmc (scppn version is 8.48.10n).
Great! Looks like maybe I just need to upgrade. I'm still running 8.42.
I just now downloaded the latest 8.5 (scppn 8.50.4n), and am still getting the earlier behavior of the destructor not being called until the function exits. Guess maybe I'll try to whittle my class down until it behaves like Heinz's class does, and discern what code makes it behave the way it currently is... Or maybe I'm just barking up the wrong tree! If C++ is free (by the standard) to call destructors when it pleases, then I just need to either: - redesign my code - or use a different compiler, and hope they don't choose to alter its destructor-call behavior. BTW, when I type "dmc ", the compiler still reports itself as version 8.42n even though scppn is 8.50.4n David
Oct 17 2007
next sibling parent Heinz Saathoff <newshsaat arcor.de> writes:
Hello,

DQNOK wrote...
 
 BTW, when I type "dmc ", the compiler still reports itself as
 version 8.42n even though scppn is 8.50.4n
dmc is only a 'driver program' that invokes the real compiler, optimizer, linker. scppn displays the version number of the real compiler. - Heinz
Oct 18 2007
prev sibling next sibling parent Heinz Saathoff <newshsaat arcor.de> writes:
Hello again,

DQNOK wrote...
 Or maybe I'm just barking up the wrong tree!  If C++ is free (by
 the standard) to call destructors when it pleases, then I just
 need to either:
  - redesign my code
  - or use a different compiler, and hope they don't choose to
 alter its destructor-call behavior.
I just looked what the standard says abaut temporaries (12.2): "A temporary bound to a reference type parameter in a function call persists until the completion of the full expression containing the call." In your example it's not the temporary object but a returned value of a member function (operators are also meber functions). The full expression is the function call. So the destructor shall be called at the end of the function and before the next one is called, just as my example does. - Heinz
Oct 18 2007
prev sibling parent reply Heinz Saathoff <newshsaat arcor.de> writes:
Hello again,

Just for your information, another problem when passing objects to  
ellipsis (as in printf). Compiling and running this code:


   class MyType
   {
   public:
       MyType(char *s) : str(s), len(strlen(s))
         { printf("ctor\n"); }
       MyType(const MyType &m) : str(m.str), len(m.len)
         { printf("copy_ctor\n"); }
       ~MyType() {  printf("dtor\n");

       char *str;
       int len;
   private:
      MyType();
   };//MyType


   void Foo(int x, ...) {}

   int main()
   {
     char c_str[] = "Ein C-String";
     MyType mt(c_str);
     Foo(1, mt);
     return 0;
   }


will give this output:

As you see there is one destructor call missing. The reason is that 
Foo() should call the destructor but due to the ellipsis doesn't know 
what was passed to it as second parameter. 


PC-Lint explains it this way:

      Passing struct 'Symbol' to ellipsis  -- A struct is being
      passed to a function at a parameter position identified by
      an ellipsis.  For example:

                void g()
                 {
                 struct A { int a; } x;
                 void f( int, ... );
                 f( 1, x );
                 ...
                 }

      This is sufficiently unusual that it is worth pointing out
      on the likelihood that this is unintended.  The situation
      becomes more severe in the case of a Non-POD struct  [10].
      In this case the behavior is considered undefined.



- Heinz
Oct 18 2007
parent reply DQNOK <davidlqualls gmail.com> writes:
== Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 Just for your information, another problem when passing objects
 to ellipsis (as in printf). Compiling and running this code:
    class MyType
    {
    public:
        MyType(char *s) : str(s), len(strlen(s))
          { printf("ctor\n"); }
        MyType(const MyType &m) : str(m.str), len(m.len)
          { printf("copy_ctor\n"); }
        ~MyType() {  printf("dtor\n");
        char *str;
        int len;
    private:
       MyType();
    };//MyType
    void Foo(int x, ...) {}
    int main()
    {
      char c_str[] = "Ein C-String";
      MyType mt(c_str);
      Foo(1, mt);
      return 0;
    }
 ...
 As you see there is one destructor call missing. The reason is
 that Foo() should call the destructor but due to the ellipsis
 doesn't know what was passed to it as second parameter.
I don't think it's Foo() that should call the destructor, is it? Since it is main() that calls the constructor to make the temporary available for the call to Foo(), then it should be main () that also calls the destructor .. I think. At least, this is the way VC works (yeah, I know they're not the standard, but it makes sense that it should work this way). This looks to me like a possible error in DMC. Turns out, this is exactly what was causing the problem in my original code. I THOUGHT that the destructor call for the temporary was being deferred, but in fact, it was never being called at all! After enough printf's I finally figured it out. David begin 644 destructor1.cpp M<BIS*2`Z('-T<BAS*2QL96XH<W1R;&5N*',I*2`-"B` ('L ('!R:6YT9B B M("!->51Y<&4H8V]N<W0 37E4>7!E)B!M*3IS='(H;2YS='(I+&QE;BAM+FQE ` end
Oct 22 2007
parent reply Heinz Saathoff <newshsaat arcor.de> writes:
Hello,


DQNOK wrote ...
 =3D=3D Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 Just for your information, another problem when passing objects
 to ellipsis (as in printf). Compiling and running this code:
    class MyType
    {
    public:
        MyType(char *s) : str(s), len(strlen(s))
          { printf("ctor\n"); }
        MyType(const MyType &m) : str(m.str), len(m.len)
          { printf("copy_ctor\n"); }
        ~MyType() {  printf("dtor\n");
        char *str;
        int len;
    private:
       MyType();
    };//MyType
    void Foo(int x, ...) {}
    int main()
    {
      char c_str[] =3D "Ein C-String";
      MyType mt(c_str);
      Foo(1, mt);
      return 0;
    }
 ...
 As you see there is one destructor call missing. The reason is
 that Foo() should call the destructor but due to the ellipsis
 doesn't know what was passed to it as second parameter.
=20 I don't think it's Foo() that should call the destructor, is it? Since it is main() that calls the constructor to make the temporary available for the call to Foo(), then it should be main () that also calls the destructor .. I think. At least, this is the way VC works (yeah, I know they're not the standard, but it makes sense that it should work this way). This looks to me like a possible error in DMC.
I have posted this question in the german usenet group=20 de.comp.lang.iso-c++ and got the answer that passing a non-POD to ... is undefined behaviour.=20 Falk Tannh=E4user quoted this text from the standard: [expr.call]/7: "When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the=20 value of the argument by invoking va_arg. The lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions=20 are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If=20 the argument has a non-POD class type, the behavior is undefined." - Heinz
Nov 01 2007
parent reply DQNOK <davidlqualls yahoo.com> writes:
== Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 I have posted this question in the german usenet group
 de.comp.lang.iso-c++
 and got the answer that passing a non-POD to ... is undefined
behaviour.
parent mikeb <mikeb example.com> writes:
== Quote from DQNOK (davidlqualls yahoo.com)'s article
 Well, I don't believe that "undefined" and erroneous are the same
 thing.
You are correct that they are not the same thing. There are erroneous constructs that are not undefined. But, undefined behavior means the compiler implementation has no requirements regarding what it must do. It doesn't need to issue a diagnostic and it doesn't have to behave in a sensible way (indeed, there may be no sensible way to behave). In other words, if your program has an undefined construct in it, you cannot count on what it may or may not do. The standard defines "undefined behavior": 1.3.12 undefined behavior [defns.undefined] behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]
Nov 14 2007
prev sibling parent reply DQNOK <davidlqualls yahoo.com> writes:
== Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 DQNOK wrote...
 I have code that really needs the destructor called when I
want it
 called.  I've tried

 { // create a new scope here
    printf( "%s", (char*)mytype(&x) ); //creates a mytype
temporary
 } // end the scope here to force mytype::~mytype on the
temporary
 //shouldn't be necessary because the temporary is out of scope
 //immediately after the printf call.
    #include <stdio.h>
    struct mytype
    {
        mytype(char *s) : str(s)
        {
            prev_char = str[0]; str[0] = 'A';
        }
        ~mytype() {str[0] = prev_char;}
        char *str;
        char prev_char;
    };//mytype
    int main()
    {
      char my_str[] = "  new way to do it";
      printf("my_type modified: %s\n", mytype(my_str));
      printf("old again: %s\n", my_str);
      return 0;
    }
 - Heinz
Heinz: I reworked your code a little to make it more reflective of my actual code. I've attached it but don't know if it'll come thru since I've never used attachments before. Surprisingly, it works as expected; the destructors are called when I think they should be called; just like in your code. Also very surprising is that there are NO CALLS TO THE COPY CONSTRUCTOR. I thought that when an object was passed as a function parameter (like to printf), the copy constructor was always called! ALWAYS CALLED! Therefore, to make things work correctly, my original copy constructor alerted the copy that it was in fact, a copy; a hack I needed to prevent the copy from misbehaving when it got destroyed. I'm thinking THIS may be the actual source of my mis-behaving code, not that the destructor is not being called. Is the compiler permitted to optimize away a copy-constructor call??? David begin 644 destructor.cpp M=')U8W0 ;7ET>7!E(`T*>R`-"B` ("!M>71Y<&4H8VAA<B`J<RD .B!S='(H M;&5N9R`](#HZ M("` ('L-"B` ("` ("` <')I;G1F*")<;FEN(&1E<W1R=6-T;W( 9F]R(%PB M='EP928 ;70I("` ("` ("` +R]C;W!Y(&-O;G-T<G5C=&]R+ T*("` ('L- M"B` ("` ("!P<FEN=&8H(EQN:6X 8V]P>2!C;VYS=')U8W1O<B!W:71H(%PB M<W1R*&EN="!O9G-T+"!I;G0 ;&5N*2\O82!N=6QL+71E<FUI;F%T960 <W5B M<W1R:6YG+ T*("` ('L-"B` ("` ("!M>71Y<&4 <F5T.PT*("` ("` (')E M="YL96YG(#T ;&5N.PT*("` ("` (')E="YS='( /2!S='(K;V9S=#L-"B` M("` ("!R970N<')E=E]T97)M(#T <F5T+G-T<EML96Y=.R`O+W)E;65M8F5R M,#L ("` ("` ("` ("` +R]N=6QL('1E<FUI;F%T92X-"B` ("` ("!R971U M<FX <F5T.PT*("` ('T-" T*("` (&]P97)A=&]R(&-H87(J(" I('L <F5T M.PT*("` (&-H87( <')E=E]T97)M.PT*?3LO+VUY='EP92`-" T*:6YT(&UA M('-U8G-T<FEN9SH 7"(E<UPB(BP *&-H87(J*2AM+G-U8EIS='(H,BPW*2DI M=&8H(EQN;W)I9VEN86P <W1R:6YG.B!<(B5S7"(B+"`H8VAA<BHI;2D[(`T* ` end
Oct 17 2007
parent reply Heinz Saathoff <newshsaat arcor.de> writes:
Hello,

DQNOK wrote...
 
 Heinz: I reworked your code a little to make it more reflective of
 my actual code.  I've attached it but don't know if it'll come
 thru since I've never used attachments before.
Got it.
 Surprisingly, it works as expected; the destructors are called
 when I think they should be called; just like in your code.  
It also does here.
 Also
 very surprising is that there are NO CALLS TO THE COPY
 CONSTRUCTOR.  I thought that when an object was passed as a
 function parameter (like to printf), the copy constructor was
 always called!  ALWAYS CALLED!  Therefore, to make things work
 correctly, my original copy constructor alerted the copy that it
 was in fact, a copy; a hack I needed to prevent the copy from
 misbehaving when it got destroyed.  I'm thinking THIS may be the
 actual source of my mis-behaving code, not that the destructor is
 not being called.
 
 Is the compiler permitted to optimize away a copy-constructor
 call???
Yes, constructor-optimization is allowed, but not required, in certain cases. I assume it's also allowed here (your second printf). It would't be allowed if you've passed 'm' directly. - Heinz
Oct 18 2007
parent DQNOK <davidlqualls gmail.com> writes:
== Quote from Heinz Saathoff (newshsaat arcor.de)'s article
 Yes, constructor-optimization is allowed, but not required, in
 certain cases.
Thanks, Heinz. I didn't know that. I'm in the process of redesigning my code to work regardless of whether the constructor gets called. For reasons like this issue we've been discussing (different compilers behaving differently), I abandoned C++ about a dozen years ago (before STL), and went to coding in straight C. My productivity soared once I stopped trying to "think in objects". Guess my brain is better wired for procedural programming. More recently though, I've been lured back to the dark side, and have been dabbling in OOP. There are nuances about C++ that I just didn't remember (like optimizing out constructor calls). I recently purchased "Effective C++" by Scott Meyers, and guess I'll be reading that too. Before C++ was all the rage, I eagerly anticipated PC Lint's ad in each issue of the C Users Journal. I always spotted the error within seconds. Once PC Lint supported C++ however, I could barely even understand their explanation of why the code was errant. Like I said, not wired for it (or maybe I'm just being lazy and don't want to learn it...)
Oct 22 2007