Topic: this == 0 for nonvirtual functions
Author: pjl@sparc6.cs.uiuc.edu (Paul Lucas)
Date: Sat, 22 Aug 1992 01:14:00 GMT Raw View
In <23512@alice.att.com> ark@alice.att.com (Andrew Koenig) writes:
>Perhaps someone might like to try to construct an example of why
>it might actually be useful to allow this==0 in a member function.
>I've been away, so I've missed some messages, but so far the only
>justification I've seen is statement like `it seems like a good idea.'
>I find it hard to imagine why it would be useful. A member function
>that never looks at `this' could just as well be made static;
>one that actually uses `this' had better not be called with this==0.
>Thus it appears that the only useful cases are:
> 1. a function that actually checks if this==0 and does different
> things depending on the result, or
> 2. a function that looks at this only if one of its arguments has
> a particular value.
>Both of these cases seem contrived to me, to the extent that I would
>expect to be able to accomplish an equivalent thing more elegantly by
>other means.
>Can someone actually come with a useful example?
My original example is for classes whose objects are dealt with
almost exclusively via pointer, e.g., canonical linked-list
classes or other container classes that use linked-lists for
their implementation.
The example I gave was the member-function:
void Link::DeleteAll() {
for ( Link *p = this, *q; p; p = q ) {
q = p->next;
delete p;
}
}
where the Link class is what you'd expect, i.e.:
class Link {
Link *prev, *next;
// ...
};
The DeleteAll() member, if pass a nil pointer, would (should) do
nothing because its this == 0 to start out.
Also consider:
Link* Link::Cut( int how_many = 1 ) {
// "cut out" how_many links starting at this
// and return a pointer to the new sub-list.
// If how_many == 0, return 0
}
void Link::Delete( int how_many = 1 ) {
Cut( how_many )->DeleteAll();
// Note that if Cut() returns nil, it would be
// if calls where this == 0 were guaranteed to
// work.
}
There are also many other examples along similar lines where not
having to check for this == 0 would be nice. Clearly, these
members can not be static.
To reiterate (for Andrew's sake), I also think that one of the
_themes_ in C++ is to lessen or eliminate tedium and errors
introduced thereby, e.g., virtual functions eliminate missing
cases in swith statements, exception-handling (when it gets
here) to eliminate explicit checks for nil pointers returned by
new() (among other things), etc. I seem my (modest) proposal in
the same light.
Again, to reiterate (for Andrew's sake), I think it's a
zero-cost feature; code that works now will continue to work,
and programmers who ignore it will be unaffected, i.e., if they
don't check 'this' their programs will exhibit undefined
behavior (by trying to access members) and "undefined behavior"
is what happens now anyway.
If nothing else, I would at _least_ like those two seemingly
contradictory paragraphs in the ARM explained, i.e., the ones
that say:
...calling a non-virtual member-function for something
that is not an object should be expected to fail
eventually... (or something along those lines)
and:
Naturally, this trick would work if the member were to
check its this pointer before accessing any members...
Does the latter sentence mean that it really _is_ ok to make the
_call_ with a this == 0, i.e., do I already have what I want?
Thanks in advance.
--
- Paul J. Lucas University of Illinois
AT&T Bell Laboratories at Urbana-Champaign
Naperville, IL pjl@cs.uiuc.edu
Author: pat@frumious.uucp (Patrick Smith)
Date: Sat, 22 Aug 1992 03:17:40 GMT Raw View
pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
|In <1992Aug20.215455.17279@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
|
|>In article <1992Aug18.045605.14220@sunb10.cs.uiuc.edu> pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
|>| To reiterate, all I would like is to guarantee that the *call*
|>| to a non-virtual member-function will succeed and that it's my
|>| responsibility to check for a non-nil 'this'.
|
|>First, note that this would not come at "zero" cost. As in the case of
|>assigning to a reference, requiring "this" to be non-null allows compilers
|>to avoid generating code to preserve nullness when adjusting pointers
|>in the case of MI. In the case of references, this issue has already
|>been reviewed and decided upon: one is not allowed to have null references.
|
| I was under the impression that for MI, once a this pointer became
| zero it has to stay zero, so it constitutes a special-case now.
| Hence, why wouldn't be a zero-cost feature?
class Derived : public Base1, public Base2 {};
Derived* p;
Base2* q = p;
// Here the compiler must guarantee that if p was 0,
// then q will be 0. This might require extra code
// to be generated and executed.
p->Base2::f();
// Here the compiler is allowed to _assume_ that p is not 0,
// so the extra code to handle the case p == 0 is not needed.
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: ark@alice.att.com (Andrew Koenig)
Date: 22 Aug 92 15:52:58 GMT Raw View
In article <1992Aug22.011400.14896@sunb10.cs.uiuc.edu> pjl@sparc6.cs.uiuc.edu (Paul Lucas) writes:
> The example I gave was the member-function:
> void Link::DeleteAll() {
> for ( Link *p = this, *q; p; p = q ) {
> q = p->next;
> delete p;
> }
> }
Because this example ultimately says `delete this' (because p == this
and it says `delete p'), I am already tempted to dismiss it out of hand.
I think it's a Bad Idea to write member functions that invalidate their
object, because it is so far away from what lots of people will expect.
Putting that aside, though, I don't see why it's any harder to make DeleteAll
a static member function with an explicit pointer argument:
/* static */ void Link::DeleteAll(Link* p) {
for (Link* q; p; p = q) {
q = p->next;
delete p;
}
}
The code is about the same length; the only difference in use is that
instead of saying
foo->DeleteAll();
you say
Link::DeleteAll(foo);
So I don't think this example is a particularly telling argument.
Ditto for the next example (omitted for brevity).
> Again, to reiterate (for Andrew's sake), I think it's a
> zero-cost feature; code that works now will continue to work,
It is not a zero-cost feature, because it would require compilers to
generate extra code for every call of a member function inherited
from a base class in the presence of virtual inheritance.
> and programmers who ignore it will be unaffected, i.e., if they
> don't check 'this' their programs will exhibit undefined
> behavior (by trying to access members) and "undefined behavior"
> is what happens now anyway.
The feature would prohibit C++ implementations from detecting this==0
as an error, which would make at least some genuine mistakes harder to find.
--
--Andrew Koenig
ark@europa.att.com
Author: jimad@microsoft.com (Jim Adcock)
Date: 25 Aug 92 19:22:45 GMT Raw View
In article <1992Aug21.044448.8282@sunb10.cs.uiuc.edu> pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
| If programmers elected not to check the this pointer, then the
| program, if a member is accessed via a null-this, would most
| likely crash or at least be undefined; this is _exactly_ the
| way language is defined now; hence, for programmers who ignore
| it, the (errant) behavior of their programs is unchanged.
|
| *****************************************************************
| Hence, adding my proposal changes nothing for those who ignore
| it.
| *****************************************************************
|
| Again, all I want is the *call* to succeed; after that, I'm on
| my own.
The call already doesn't necessarily succeed in the virtual case.
The call already doesn't necessarily succeed in the null-this-from-a-ref case.
You are asking for support for a special case where a non-virtual member
function is invoked via a null pointer. Asking for this singular special
case seems awfully non-orthogonal to me. If you were to insist on this,
then it should work for null-refs and for virtual functions....
| Perhaps [with regard to users checking null tests]; but I think
| that one of the _themes_ of programming in C++ is to eliminate
| tedium and errors introduced thereby: virtual functions
| eliminate switch statements missing a case; exception-handling
| will eliminate having to check the return value from new to see
| if it failed, etc.
|
| I see my (modest) proposal in the same light.
In the early days of Objective-C method invocation on a null pointer was
defined to be a no-op. The result was intended to save programmers from
having to handle special cases. In practice the result was that it took
about a week to track down each case where some method was invoked on
a null pointer -- because the result of such a mistake was a null pointer
-- resulting in another no-op method call, resulting in a null pointer --
resulting in another no-op call. Fortunately, we had access to source and
quickly patched the method dispatch routine so that invoking a method via
a null pointer resulted instead in an assert. Then bugs were caught quickly.
Therefore I suggest rather than allowing method invocation on null pointers
what one really needs is a good way to always assert on such "mistakes."
Allowing overloadable operator dot is one step in this direction. Further,
defining null ptr invocations as being illegal *allows* quality compiler
implementations the option of inserting runtime checks on such illegalities
during debug mode. Better yet would be if C++ supported better ways to specify
class invarients checking on method dispatch and return.
Author: juul@diku.dk (Anders Juul Munch)
Date: 25 Aug 92 17:28:50 GMT Raw View
pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
> void Link::DeleteAll() {
> for ( Link *p = this, *q; p; p = q ) {
> q = p->next;
> delete p;
> }
> }
You are deleting the first list member using its own DeleteAll method and
the other members using the first member's DeleteAll method.
To see what the problems can arise from this, make DeleteAll virtual and
redefine it in a subclass:
class MyLink : Link {
void DeleteAll() { ExtraBookkeeping(); Link::DeleteAll(); }
};
Now MyLink's will be DeleteAll'ed with the wrong method if a plain Link
heads the list. And to make things worse, such a bug would be almost
impossible to find.
However,
static Link::DeleteAll(Link* head);
does not have this problem and also note that `head' can legally be NULL,
solving your original problem.
Anders Munch | Department of Computer Science
juul@diku.dk | University of Copenhagen, Denmark
"Confused? The thing is to get confused at a higher level" - Jorgen Thorslund
Author: pjl@sparc10.cs.uiuc.edu (Paul Lucas)
Date: Tue, 18 Aug 1992 04:56:05 GMT Raw View
The ARM, p. 176 says:
The effect of calling nonstatic member function of a
class C for something that is not an object of class C
is undefined.
For example,
((X*)0)->f();
is not guaratneed to work. ... Even for non-virtual
functions, one should expect this trick to fail
eventually because specialized C++ implementations might
assume something about the contents of objects even when
calling nonvirtual functions. In particular, on might
expect implementaions instrumented for debugging,
interpretersm and implementations supporting dynamic
loading to be sensitive to "housekeeping" information
placed in the objects by the compiler.
Natually, this trick would work only f() either checks
its 'this' pointer before accessing any members or...
IMHO, the ability to *depend upon* the (desired) fact that
*calling* a non-virtual member-function with a nil pointer is
*guaranteed* to be harmless seems worthwhile (and its addition
to the language wouldn't break anything as far as I can tell).
The alleged existence of "specialized C++ implementations" seems
rather a "fringe" reason for saying that it's undefined; the
standard could *make* it defined.
The last paragraph is also vague; does it really mean "...this
trick is *guaranteed* to work only if f() either checks its
'this' pointer before accessing any members...?"
The simple example where this is handly is a linked-list class
where the objects are always dealt with via pointer. The class,
in its quest for robustness, could easily check its 'this'
pointer before doing anything; consider also:
void Link::DeleteAll() {
for ( Link *p = this, *q; p; p = q ) {
q = p->next;
delete p;
}
}
There's an initial test on 'this' so if I call DeleteAll() with
a nil pointer, it should do nothing. It would be nice to not
have to check for nil in such cases; requiring it is prone to
user error.
To reiterate, all I would like is to guarantee that the *call*
to a non-virtual member-function will succeed and that it's my
responsibility to check for a non-nil 'this'.
Comments?
--
- Paul J. Lucas University of Illinois
AT&T Bell Laboratories at Urbana-Champaign
Naperville, IL pjl@cs.uiuc.edu
Author: pat@frumious.uucp (Patrick Smith)
Date: Wed, 19 Aug 1992 04:22:14 GMT Raw View
pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
| IMHO, the ability to *depend upon* the (desired) fact that
| *calling* a non-virtual member-function with a nil pointer is
| *guaranteed* to be harmless seems worthwhile (and its addition
| to the language wouldn't break anything as far as I can tell).
If this change to the language were made, it might be appropriate
not to include the case of calling a member function with a nil
pointer to a class virtually derived from the class where the
function is defined:
class Base { void f(); };
class Derived : public virtual Base {};
...
Derived* p;
p->f();
If I understand correctly, in most implementations the conversion
of p to a Base* will require examining the contents of *p.
If p may be 0, then the compiler must insert extra code to
treat this case correctly.
Of course, this extra code is currently necessary for all conversions
of a Derived* to a Base* where the compiler can't be sure that
the pointer is non-nil. So perhaps the extra overhead for the
function calls wouldn't be seen as significant.
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: jimad@microsoft.com (Jim Adcock)
Date: 20 Aug 92 21:54:55 GMT Raw View
In article <1992Aug18.045605.14220@sunb10.cs.uiuc.edu> pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
| To reiterate, all I would like is to guarantee that the *call*
| to a non-virtual member-function will succeed and that it's my
| responsibility to check for a non-nil 'this'.
First, note that this would not come at "zero" cost. As in the case of
assigning to a reference, requiring "this" to be non-null allows compilers
to avoid generating code to preserve nullness when adjusting pointers
in the case of MI. In the case of references, this issue has already
been reviewed and decided upon: one is not allowed to have null references.
Further, note that member functions can be called either on a pointer or
on a reference. If the language were to PERMIT calling member functions
on a null pointer, such would not ALLOW *you* to test for null "this" inside
you member functions, but rather would REQUIRE all of *us* to test for
null "this" inside ALL *our* non-virtual member functions -- because a user
of our class such as yourself could then legally call *our* member functions via
a null pointer. If such were the case then, the only sane remedy C++
programmers would have would be to declare all their member functions virtual.
Then *you* *still* could not legally call these members on a null pointer --
but many member functions would now be needlessly slow and generate needlessly
large code.
I suggess instead that you use friend functions or static member functions
where you feel you must address null pointers implicitly. Alternately, note
that the users of your class may be the ones in the best position to perform
the null pointer tests [explicitly].
Author: pjl@sparc10.cs.uiuc.edu (Paul Lucas)
Date: 21 Aug 92 04:44:48 GMT Raw View
In <1992Aug20.215455.17279@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
>In article <1992Aug18.045605.14220@sunb10.cs.uiuc.edu> pjl@sparc10.cs.uiuc.edu (Paul Lucas) writes:
>| To reiterate, all I would like is to guarantee that the *call*
>| to a non-virtual member-function will succeed and that it's my
>| responsibility to check for a non-nil 'this'.
>First, note that this would not come at "zero" cost. As in the case of
>assigning to a reference, requiring "this" to be non-null allows compilers
>to avoid generating code to preserve nullness when adjusting pointers
>in the case of MI. In the case of references, this issue has already
>been reviewed and decided upon: one is not allowed to have null references.
I was under the impression that for MI, once a this pointer became
zero it has to stay zero, so it constitutes a special-case now.
Hence, why wouldn't be a zero-cost feature?
>Further, note that member functions can be called either on a pointer or
>on a reference. If the language were to PERMIT calling member functions
>on a null pointer, such would not ALLOW *you* to test for null "this" inside
>you member functions, but rather would REQUIRE all of *us* to test for
>null "this" inside ALL *our* non-virtual member functions -- because a user
>of our class such as yourself could then legally call *our* member functions via
>a null pointer. If such were the case then, the only sane remedy C++
>programmers would have would be to declare all their member functions virtual.
>Then *you* *still* could not legally call these members on a null pointer --
>but many member functions would now be needlessly slow and generate needlessly
>large code.
I don't think that it would require everyone to test this for
zero. I had said that this would be usedful only if a majority
of member-function calls were made via a pointer, i.e.,
linked-list and perhaps other container classes that use linked
lists for their implementation.
Such a class would have as part of it's documentation: "This
class is null-this safe" meaning it would do something sensible,
or more likely nothing at all, for a null this pointer.
Other "concrete object" type classes, i.e., complex numbers,
would have no need of being "null-this safe."
It wasn't my intent to introduce the feature just so all
programmers could forbid it by making everything virtual.
If programmers elected not to check the this pointer, then the
program, if a member is accessed via a null-this, would most
likely crash or at least be undefined; this is _exactly_ the
way language is defined now; hence, for programmers who ignore
it, the (errant) behavior of their programs is unchanged.
*****************************************************************
Hence, adding my proposal changes nothing for those who ignore
it.
*****************************************************************
Again, all I want is the *call* to succeed; after that, I'm on
my own.
>I suggess instead that you use friend functions or static member functions
>where you feel you must address null pointers implicitly. Alternately, note
>that the users of your class may be the ones in the best position to perform
>the null pointer tests [explicitly].
Perhaps [with regard to users checking null tests]; but I think
that one of the _themes_ of programming in C++ is to eliminate
tedium and errors introduced thereby: virtual functions
eliminate switch statements missing a case; exception-handling
will eliminate having to check the return value from new to see
if it failed, etc.
I see my (modest) proposal in the same light.
Further comments?
--
- Paul J. Lucas University of Illinois
AT&T Bell Laboratories at Urbana-Champaign
Naperville, IL pjl@cs.uiuc.edu
Author: ark@alice.att.com (Andrew Koenig)
Date: 21 Aug 92 14:30:01 GMT Raw View
Perhaps someone might like to try to construct an example of why
it might actually be useful to allow this==0 in a member function.
I've been away, so I've missed some messages, but so far the only
justification I've seen is statement like `it seems like a good idea.'
I find it hard to imagine why it would be useful. A member function
that never looks at `this' could just as well be made static;
one that actually uses `this' had better not be called with this==0.
Thus it appears that the only useful cases are:
1. a function that actually checks if this==0 and does different
things depending on the result, or
2. a function that looks at this only if one of its arguments has
a particular value.
Both of these cases seem contrived to me, to the extent that I would
expect to be able to accomplish an equivalent thing more elegantly by
other means.
Can someone actually come with a useful example?
--
--Andrew Koenig
ark@europa.att.com