Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!uunet!microsoft!jimad From: jimad@microsoft.UUCP (Jim ADCOCK) Newsgroups: comp.lang.c++ Subject: Re: Smarter pointers - another solution Message-ID: <70279@microsoft.UUCP> Date: 29 Jan 91 00:23:54 GMT References: <3454@lupine.NCD.COM> Reply-To: jimad@microsoft.UUCP (Jim ADCOCK) Organization: Microsoft Corp., Redmond WA Lines: 146 In article <3454@lupine.NCD.COM> rfg@lupine.ncd.com (Ron Guilmette) writes: |If you have no interest in the issue of smart pointers (and how their |use could be made safer) then hit `n' now. | .... |When using so-called "smart pointers" for some controlled class C, the |programmer has no way to take complete control over the creation and use |of value of type T* and T& (i.e. "dumb pointers" and "dumb references" |to the class C). This fact makes it difficult to insure that smart |pointers to some class type (T) are used safely and consistantly throughout |the program. My claim is that it is oxymoronic to try to make a "smart" version of a fundamentally "dumb" thing. C++ pointers are fundamentally dumb things. [C++ references aren't fundamentally dumb -- they're just crippled.] C++ pointers are fundamentally dumb things because they are literally designed to be "placemarkers" into uniform _arrays_ of things -- but instead they are commonly used to reference _individual_ objects, and in C/C++ an individual object is not the same thing as an array of one object. Thus, since I claim C/C++ pointers are fundamentally brain-dead, I would encourage you to give up on them, and encourage you instead to think about fixing the crippled aspects of C++ references, and the limitations that keep one from creating smart references. One obvious limitation that keeps one from easily creating a smart reference is the current restriction that one is not allowed to overload operator.() [operator dot] the way one can currently overload operator->() Another obvious limitation on C++ references is that they are always constant -- there is no way to re-assign to them after initialization, since op= is pre-defined to mean "copy the referenced object." Since the obvious operators already have pre-defined meaning, allowing re-assignment of references in C++ would require new syntax: perhaps an op:= , for instance. |There are five mechanisms by which valid non-null values of type T* or T& |(where the type T is a class type) may be generated. These are: | | 1) A value of type T* may be generated by applying the unary | & operator to a value of type T. As you point out, we already have control over this problem. | 2) A value of type T* may be implicitly generated (by the | compiler) for each call to a non-static member function | of the class type T. (This refers to the value that the | compiler supplies for the `this' pointer.) I claim "this" problem is the hard one to fix. First, it gets into the bowels of a compiler's stack and calling sequence generation. Second, member functions shouldn't have been defined to pass a "this" pointer to the object invoked upon -- because the tacit assumption is that that object is an element of a uniform array, and typically it is not. Instead, C++ should have been defined to pass a reference to "self." "This" damage being done, I don't see how to undo it, nor do I see an easy way for compilers to generate code based on generalized "smart this pointers." As member functions currently can take a "const" on the implied this pointer, perhaps you could add to the syntax where a "&" or "const &" imply that a "self reference" rather than a "this pointer" is desired for a particular member function. Perhaps you could generalize this to where an exact "smart reference type" would be accepted where the present "const" is allowed: // use a smart reference instead of "this" void Foo::doSomething() smart_Foo_reference; // use a plain "self" reference instead of a "this" pointer void Foo::doSomething() &; This is only the syntax, of course -- the code generation problem would remain. | 3) A value of type T* may be generated via the implicit | application of the unary & operator to a value of type | "array of T" (yielding the address of the zeroth element | of the array). 3) is one of the great type weaknesses of C/C++ IMO. In use, a Foo[0..100] gets automatically converted to a Foo[+-??? .. +-???], which the user typically thinks of as just being a Foo. When garbage collecting, given a Foo*, what objects in Foo[0..100] are aliased [and thus kept alive] by the Foo*? Explain your answer, and explain a reasonable way to generate code to handle this GC issue. | 4) A value of type T* (or T&) may be generated via an explicit | or implicit cast from a different pointer type (or reference | type) value (or an integral type value) to type T* (or T&). More of the same problem. Basically C++ allows a programmer to treat a foo& the same as a foo[1] the same as foo[12] of foo[0..100] -- except in issues of optimization and memory management they are not at all the same. | 5) A value of type T& may be generated via the initialization of | a variable of type T& with an object of type T. 6) A value of type T* may be generated by adding or subtracting a variable of integral type to a type T* either directly, or via [] notation. More of the same problems. My conclusions: Give up on pointers for serious C++ programming. Enable the serious use of references instead, including providing the facilities necessary for smart references. If a programmer needs an array of Foos, have the programmer declare an array and a smart_ref> if an alias for that array of Foos is needed. Index into an arrayFoo using arrayFoo[12] syntax, not using pointers. The arrayFoo is now a distinct object, held alive via a smart reference of the correct type, referring to the start of the arrayFoo object. What is aliased and thus to be kept alive is now clear and unambiguous. If people use references "everywhere" excepting when doing primitive "C/asm" programming, the syntax of C++ becomes uniform: object.doSomething(); not: (*object).doSomething(); object->doSomething; object.doSomething(); object[0].doSomething(); ....taken to the Nth power where N is the number of parameters and return types. Finally, even if one fixed the current problems with pointers and references as mentioned above, one would only have an "enabling" solution, not a "supported" solution. Creating classes that actually use these smart references would still remain a tremendously difficult task, that must be correctly performed in each derived class. If C++ added a mechanism whereby "smart references" would need to be defined only once per inheritence hierarchy, and thereafter derived classes would always "do the right thing" -- only then could C++ be said to "support" smart references. [Realistic people note all these problems, then say: "fat chance!" to fixing them. Personally, I'm less than completely realistic.]