[comp.lang.c++] Interesting "Feature" of C++

wfp@dasys1.UUCP (William Phillips) (10/20/87)

I have discovered an interesting "feature" of C++ (undocumented, as far as I
know) which takes effect when using virtual functions and assignments
to  this .

Briefly, given a base class and derived class(es) containing virtual function(s),
if your program makes an assignment to  this , you may be in trouble.
Example:

struct base {
	.
	.
	base *something();		// defined elsewhere
	base *something_else();
	virtual void whatever();
	.
	.
	.
}

struct derived : base {
	.
	.
	.
	void whatever();
	.
	.
	.
}

	.
	.
	.
	this = something();
	if (this == NULL) {
		this = something_else();
	}
	.
	.
	.

A fragment similar to the above, if the result of the assignment
	this = something();
is in fact null, will (on a Silicon Graphics Iris workstation)
cause a segmentation violation at runtime.  If you explicitly assign
	this = NULL;	(or this = 0;)
the problem will not occur.  The reason is that the C code generated by
cfront in the first case does more than assigning the result of a
function call; it also sets a pointer to an array of pointers to the
virtual functions.  It does not generate code to check the
value of  this  before attempting to set the pointer, however.  Thus, if this
is NULL, the program attempts to set the function pointer to some bizarre
value, and crashes.  An explicit assignment of 0 to  this  does not generate
code for changing the function pointer, and so causes no problem.  The
workaround is simple, but annoying (untidy!):

	base *that = something();
	if (that == NULL) {
		that = something_else();
	}
	this = that;
	.
	.
	.
or something like that. 

Any comments?


-- 
William Phillips                 {allegra,philabs,cmcl2}!phri\
Big Electric Cat Public Unix           {bellcore,cmcl2}!cucard!dasys1!wfp
New York, NY, USA                (-: Just say "NO" to OS/2! :-)

dave@spool.wisc.edu (Dave Cohrs) (10/21/87)

In article <1718@dasys1.UUCP> wfp@dasys1.UUCP (William Phillips) writes
about a problem with changing "this" in a constructor.

Sorry, but I don't have a solution, but I have an even more painful
problem.  If we take William's example and expand it a bit (I added
a constructor to "base":

>struct base {
>	.
>	.
    base();					// a constructor for base
>	base *something();		// defined elsewhere
>	base *something_else();
>	virtual void whatever();
>	.
>	.
>	.
>}
>
>struct derived : base {
>	.
>	.
>	.
>	void whatever();
>	.
>	.
>	.
>}
>
	derived* something();
>	.
>	.
>	.
>	this = something();
>	if (this == NULL) {
>		this = something_else();
>	}
>	.
>	.
>	.
>

Now, when you reassign "this", the base constructor gets called to
reinitialize the data.  This is very harmful in two cases, if you set
this to nil, or if you set this to an allready allocated and
initialized object.

In the first case, if you say "this = 0" (yes, explicit assignments are
just as bad), or in any way set this to a nil pointer, base::base will
reallocate space for this and this in "derived" will not be nil after
the assignment.

In the second case, say that you have a list of objects of type
"derived".  Have the derived constructor search the list until it finds
an appropriate object, and then assigns this to that object.  Once
again, the base constructor is called, possibly destroying all useful
data in the object.

Actually, this behaviour is documented.  See sec. 8.5.8 in the C++ book.
Think of the virtual table pointer as a member (albeit invisible) of
the object.  Then, following the rules in 8.5.8, the table pointer
gets set after reassigning "this".  Note that I saw no code to check
if this was set to nil in any case.


Dave Cohrs                                  Still single after all these years
+1 608 262-6617                        UW-Madison Computer Sciences Department
dave@cs.wisc.edu                 ...!{harvard,ihnp4,rutgers,ucbvax}!uwvax!dave

wfp@dasys1.UUCP (William Phillips) (10/24/87)

(Please note - my host's news editor just choked on this; I am trying to
reconstruct what I was just wrapping up when it died.)

In article <4513@spool.wisc.edu>, dave@spool.wisc.edu (Dave Cohrs) writes:
> In article <1718@dasys1.UUCP> wfp@dasys1.UUCP (William Phillips) writes
> about a problem with changing "this" in a constructor.
> 
> Sorry, but I don't have a solution, but I have an even more painful
> problem.  If we take William's example and expand it a bit (I added
> a constructor to "base":

[example deleted]
 
> Now, when you reassign "this", the base constructor gets called to
> reinitialize the data.  This is very harmful in two cases, if you set
> this to nil, or if you set this to an allready allocated and
> initialized object.

> In the first case, if you say "this = 0" (yes, explicit assignments are
> just as bad), or in any way set this to a nil pointer, base::base will
> reallocate space for this and this in "derived" will not be nil after
> the assignment.
 
I'm not quite sure I understand what you're saying, but if you look at
section 7.7 of <The Book (the _only_ book, ghod helpus :-)>, it is
stated that an assignment to  this  in a derived class constructor will
delay the execution of the base class constructor until after such 
assignment; I have been using this technique successfully to manipulate
some data needed by the base class constructor within the derived class
constructors; example:

	cnst::cnst(int i) : (s, "int", literal_t) {
		sprintf(s, "%d", i);
	// Force base class ctor to wait till sprintf done
		this = NULL;
		ic++;
	}

which works fine to get s formatted the way I want it before calling
cnst's base class constructor with s as an argument. 
 
> In the second case, say that you have a list of objects of type
> "derived".  Have the derived constructor search the list until it finds
> an appropriate object, and then assigns this to that object.  Once
> again, the base constructor is called, possibly destroying all useful
> data in the object.

Aha!  That may explain a particularly vexing problem I was wrestling with
earlier this week.  I was in fact searching a list, finding an appropriate
object, and assigning this to it (er, from it, I think!).  If the found
object passed certain tests, it was modified slightly, but otherwise left
alone; no problem.  If it failed those tests but passed others, I created
a new object of the same class and (thought) I left the original alone;
however, I found that the new object was fine, but the original one had
been stripped down to its base class and consequently lost all its extra
derived class information (the virtual functions that printed out the
contents and class of each object for debugging purposes saw the original
object as a base object !)  I _had_ had the impression from some earlier
attempts and what I could glean from <The ^**%$ Book> that once an object
was constructed its class was cast in stone; apparently not so!  In fact,
the reason I was using the " this = NULL " technique above was because I
had tried to construct a base class object and then "recast" it into a
derived class object after the fact.  Didn't work!
 
> Actually, this behaviour is documented.  See sec. 8.5.8 in the C++ book.
> Think of the virtual table pointer as a member (albeit invisible) of
> the object.  Then, following the rules in 8.5.8, the table pointer
> gets set after reassigning "this".  Note that I saw no code to check
> if this was set to nil in any case.

If you look at the C code generated by cfront (if you can stomach it :-),
you will see that the assignment

	this = NULL
generates
	_auto_this = 0

...and that's all

whereas an "inexplicit" assignment of this (such as  " this = funct() " )
additionally generates code to reassign the virtual table pointer, which
will cause severe distress if the value of this is something weird like 0.

P.S.:  I have been told that <The Book> is (a)incomplete and (b)full of
errors; I believe it.  It also doesn't have an index worth a damn.  I
_wish_ Harbison & Steele would do for C++ what they did for C.


-- 
William Phillips                 {allegra,philabs,cmcl2}!phri\
Big Electric Cat Public Unix           {bellcore,cmcl2}!cucard!dasys1!wfp
New York, NY, USA                (-: Just say "NO" to OS/2! :-)

mikem@otc.oz (Mike Mowbray) (10/26/87)

In article <1787@dasys1.UUCP>, wfp@dasys1.UUCP (William Phillips) says:

>> ... about a problem with changing "this" in a constructor. [ or elsewhere ]

Seems to me that it's not widely understood that "this" is special, (after
all, it's a keyword, not a normal variable), and should not be treated
simply as a pointer that you can do arbitrary things to. (At least not in
the current version of C++;- I recall a hint on the net some time ago that
assignment to this is being abolished in favour of something better.)

> I was in fact searching a list, finding an appropriate object, and
> assigning this to it (er, from it, I think!).

It makes a big difference. I assume that "this = something;" is what's meant.

> If the found object passed certain tests, it was modified slightly, but
> otherwise left alone; no problem.  If it failed those tests but passed
> others, I created a new object of the same class and (thought) I left
> the original alone; however, I found that the new object was fine, but
> the original one had been stripped down to its base class and
> consequently lost all its extra derived class information [...]

This would seem a highly inappropriate use of "this". Assigning to this
says that you want the current object (whose member function you're
currently inside) to become something else.

> I _had_ had the impression from some earlier attempts and what I could
> glean from <The ^**%$ Book> that once an object was constructed its
> class was cast in stone; apparently not so!

Well, it is unless one cro-bars cfront, asking it to make the object something
else, as is the case here.

> In fact, the reason I was using the " this = NULL " technique above was
> because I had tried to construct a base class object and then "recast"
> it into a derived class object after the fact.  Didn't work!

Once again, this would be a misunderstanding of what construction and objects
are all about. It's unreliable for the user to tell the compiler that he/she
now wants a certain object to be treated as something different. The way to
do this is to rely on derived class constructors. For example, one could
have a constructor derived::derived(base&) that makes a derived from a
base. Alternatively, it may be that the user has lost sight of the *real*
reason for wanting to do this sort of thing in the first place. Usually, it
is because some behaviour is desired from the object, dependent on what
type of object it is. A re-thinking of the problem, using virtual
functions, can solve this much more reliably and cleanly in a large number
of cases.

> Many of the problems encountered in learning C++ arise from the desire
> not to use object-oriented programming style. Similarly, most can be
> overcome by looking very carefully at whether one is trying to force
> certain behaviour based on the type of an object, without letting the
> compiler keep track of what the type is, by way of virtual functions.
> Note that in many cases, this is all that's required. - The programmer
> only wanted to know the type of the object so he could do something
> with it. This is largely solved by careful class design using virtual
> member functions.

> If you look at the C code generated by cfront (if you can stomach it :-),
> you will see that the assignment
> 
> 	this = NULL
> generates
> 	_auto_this = 0
> 
> ...and that's all
> 
> whereas an "inexplicit" assignment of this (such as  " this = funct() " )
> additionally generates code to reassign the virtual table pointer, which
> will cause severe distress if the value of this is something weird like 0.

I guess this is a hangover from the fact that there is one case when this=0 makes
sense, namely in the destructor. However, I am certainly eager to see something
better than the assignment-to-this method of user-controlled storage allocation.

> P.S.:  I have been told that <The Book> is (a)incomplete and (b)full of
> errors; I believe it.  It also doesn't have an index worth a damn.
> _wish_ Harbison & Steele would do for C++ what they did for C.

Ummm. Hang on. Let's put things into perspective. C++ is a large improvement over
C, and it's occasional shortcomings are second-order by comparison. Similarly, the
book is a large improvement over no-book, and, *if one follows the object-oriented
style of programming*, is quite good, although it could of course be improved further.
The more subtle aspects of C++ behaviour are not too difficult to discover from the
generated code. (I don't know why people are always complaining about the generated
code. - A few simple modifications to the +i option in the CC script produces code
that can be comprehended ok. I'll post these mods in a separate article.)

Regarding errors in the book, Bjarne posted a list of corrections some time ago.
Perhaps a re-post is appropriate?

			Mike Mowbray
		    Systems Development
			|||| OTC ||

	    ACSnet:  mikem@otc.oz	UUCP:  {uunet,mcvax}!otc.oz!mikem 
	     Snail:  GPO Box 7000, Sydney 2001, Australia

alastair@geovision.UUCP (Alastair Mayer) (11/02/87)

In article <265@otc.oz> mikem@otc.oz (Mike Mowbray) writes:
>Seems to me that it's not widely understood that "this" is special, (after
>all, it's a keyword, not a normal variable), and should not be treated
>simply as a pointer that you can do arbitrary things to. (At least not in
>the current version of C++;- I recall a hint on the net some time ago that
>assignment to this is being abolished in favour of something better.)

Aaarrrgghhh!!  I certainly hope not!  Assigning to this can be very
useful, most particularly in operations on lists of objects, where
a loop like
            while (this) {  // while (this!=NULL) for the picky
               // do something
               this = next;
            }
will do something to the entire list when invoked in the object at
the head of the list.  Adding other termination conditions allow for
other operations, such as finding an item in a list.
There may be other ways to do it without assigning to this, but
I remain to be convinced that they're "better".
-- 
 Alastair JW Mayer     BIX: al
                      UUCP: ...!utzoo!dciem!nrcaer!cognos!geovision!alastair

 "What we really need is a good 5-cent/gram launch vehicle."