Archives
D Programming
D
D.gnu
digitalmars.D
digitalmars.D.bugs
digitalmars.D.dtl
digitalmars.D.dwt
digitalmars.D.announce
digitalmars.D.learn
digitalmars.D.debugger
C/C++ Programming
c++
c++.announce
c++.atl
c++.beta
c++.chat
c++.command-line
c++.dos
c++.dos.16-bits
c++.dos.32-bits
c++.idde
c++.mfc
c++.rtl
c++.stl
c++.stl.hp
c++.stl.port
c++.stl.sgi
c++.stlsoft
c++.windows
c++.windows.16-bits
c++.windows.32-bits
c++.wxwindows
digitalmars.empire
digitalmars.DMDScript
electronics
|
c++ - When destructors are called
↑ ↓ ← → 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
↑ ↓ ← → Walter Bright <newshound1 digitalmars.com> writes:
If you could boil this down to a small test case, I'd appreciate it.
↑ ↓ ← → 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
↑ ↓ ← → 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
called. I've tried
{ // create a new scope here
printf( "%s", (char*)mytype(&x) ); //creates a mytype
} // end the scope here to force mytype::~mytype on the
//shouldn't be necessary because the temporary is out of scope
//immediately after the printf call.
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*.
to return the correct character pointer member of mytype. It
works fine.
This example relies on object layout and is therefore not
- Heinz
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.
↑ ↓ ← → Heinz Saathoff <newshsaat arcor.de> writes:
Hello,
DQNOK wrote...
You create a temporary 'mytype' and
cast this object to a 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
- Heinz
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
↑ ↓ ← → 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
↑ ↓ ← → 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).
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
↑ ↓ ← → 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
↑ ↓ ← → 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
↑ ↓ ← → 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
↑ ↓ ← → 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
↑ ↓ ← → 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.
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
↑ ↓ ← → 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
Falk Tannhäuser quoted this text from the standard:
[expr.call]/7:
"When there is no parameter for a given argument, the
passed in such a way that the receiving function can obtain
value of the argument by invoking va_arg. The lvalue-to-
array-to-pointer, and function-to-pointer standard
are performed on the argument expression. After these
if the argument does not have arithmetic, enumeration,
pointer to member, or class type, the program is ill-formed.
the argument has a non-POD class type, the behavior is
- Heinz
Well, I don't believe that "undefined" and erroneous are the same
thing.
From the reading I've done since my last post, I am now convinced
that if a C++ compiler calls a constructor, it MUST also call a
matching destructor. We have demonstrated code where DMC does not
follow this behavior. THAT is a bug.
I knew when I wrote printf("%p", myType(x)) it was a hack; it was
just easier than doing the proper cast. DMC was free to choose
how to push the values of x onto the stack. That behavior IS
undefined. The fact that it pushed the cptr member when it did
was the only reason my hack even sort-of worked. If it had pushed
the values in a different order, I would not have had reason to
complain. However, once it called a myType constructor, it should
have also called a myType destructor on the same variable. That
requirement is not undefined.
Regarding what you said about Foo() calling the destructor, well,
I'd be shocked if Walter really attempted to make a variadic
function do it's own stack-cleanup. It's not illegal, more like
just impossible.
Anyway, I've definitely learned something with your help regarding
not using non-POD types in variadic functions. I'll remove the
hack from my test code.
Thanks for your help, and for doing the research.
David
BTW, here's a little quote from a site that Walter pointed out. I
think it's relevant.
-------------
Empirical studies indicate that 20% of the people drink 80% of the
beer. With C++ developers, the rule is that 80% of the developers
understand at most 20% of the language. It is not the same 20% for
different people, so don't count on them to understand each
other's code.
http://yosefk.com/c++fqa/picture.html#fqa-6.6
-------------
↑ ↓ ← → 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. ]
↑ ↓ ← → 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
called. I've tried
{ // create a new scope here
printf( "%s", (char*)mytype(&x) ); //creates a mytype
} // end the scope here to force mytype::~mytype on the
//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
↑ ↓ ← → 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
↑ ↓ ← → 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...)
|
|