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."