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