Topic: No such thing as smart pointers


Author: konigsba@cambridge (Dan Konigsbach)
Date: 24 Jun 94 23:46:28 GMT
Raw View
>In article <TKORVOLA.94Jun3190725@dopey.hut.fi> Timo.Korvola@hut.fi writes:
>>
>>I have been working on a program that makes heavy use of polymorphism.
>>In C++ this requires one to deal with objects by reference, passing
>>around pointers or references instead of actual objects, which are
>>often dynamically allocated.  Thus the shortcomings of the C++
>>dynamic memory management have become a problem.
>>
>>Sometimes you need to store a copy of an object or a pointer to it
>>somewhere, e.g. as part of another object.  You can't make a copy
>>unless you know the exact type of the object (or use a rather ugly
>>virtual function kluge), and copying large objects can cause
>>significant and useless overhead.  But if you store a pointer and the
>>original object gets deleted prematurely, you're in trouble.

>[clip]

Two possible (related) approaches.  Coplien describes in (darn, I can't
think of the title, is it "Advanced C++"?) something called the envelope/
letter paradigm, in which one class (the envelope) points to and forwards
methods to another class (the letter).  Both the envelope class and the
letter class (which may, itself, be polymorphic) are derived from a common
base, so someone using one of these objects doesn't need to know whether
it is pointing to a a letter (of whatever type letters come in) or an
envelope containing a letter.  If fact, the contents of a envelope can
change while you're pointing to it, in effect changing the thing you're
pointing to, without any problem.

It's a powerful technique, and Coplien gives good example of using it,
using numbers.

It might, however, be overkill for the problem you describe.

A simpler approach that might suit your needs is the "counted pointer"
approach.  It's popular for string classes, but very generally useful:

It's also a two-class approach.  The outer class, which the outside
world sees, has all of the methods that the users of the class expect.
Internally, it is a pointer to an inner object, which actually is the
thing being worked on.  The outer class forwards all methods to the
inner class.  The inner class can ONLY be accessed via the outer class,
a big difference from envelope/letter.  The inner class keeps a count
of how many outer classes point to it - that count is maintained by
methods invoked by the outer class.

Whenever the counter for the inner object goes to zero, the inner object
is destroyed.  (That logic, of course, has to be in the outer class,
since objects can't self-terminate.)  Multiple outer objects can point
to the same inner object.  When an outer object needs to perform a
non-const operation on the inner object, it checks if the use count
of the inner object is greater than one.  If so, it constructs a new
duplicate copy of the inner object, points to it, instead, and performs
the operation on the copy.  The new copy now has a use count of one,
and the older, shared copy's use count has just gone down by one.
This is "copy on modification" logic.

Assignment operators and copy constructions are now very, very quick:
An assignment operator first decrements the use count of the object
it is currently pointing to, possibly triggering that object's
destruction.  Then, for either assignment or copy it does is make
a copy of the pointer to the inner object, and increments that object's
use count.

This is a great technique, and not at all as hard to implement as the
description above might make it seem.  It also makes using the object
a breeze.  Clients never see the inner object.  You never worry about
new-ing or deleting the object; memory management is virtually automatic.
The outer object is so small that copying it (including passing  and
returning by value) is a non-issue.

Hope one or the other of these prove useful.  (And if you already
knew everything I just said, I apologize.  Possibly others reading
this didn't, and it proved useful to them.)

-  Dan






Author: rjl@f111.iassf.easams.com.au (Rohan LENARD)
Date: 21 Jun 1994 07:31:12 +1000
Raw View
In article <1994Jun9.180502.19611@smartstar.com>,
Frederic Deramat <frederic@smartstar.com> wrote:
>Timo Korvola (tkorvola@dopey.hut.fi) wrote:
[ ..snip.. ]
>
>Do all the conversions described in the example below correspond to explicitly
>stated rules of the (upcoming) C++ standard or are some compilers simply being
>overzealous?

Regardless of whether it is valid or not, it will be possible with
template member functions to an a conversion operator which does what you
want.

eg.

template <class T> Ptr {
public:
  Ptr(T* p);

...

  template <class V> operator Ptr<V>();

}

Unfortunately this relaxes the type system a bit, because it makes some
explicit conversions implicit :-(

Regards,
 Rohan
--
------------------------------------------------------------------------
rjl@iassf.easams.com.au |
Rohan Lenard            |                .sig on holiday
+61-2-367-4555          |




Author: jason@cygnus.com (Jason Merrill)
Date: Tue, 21 Jun 1994 06:22:04 GMT
Raw View
>>>>> Rohan LENARD <rjl@f111.iassf.easams.com.au> writes:

> template <class T> Ptr {
> public:
>   template <class V> operator Ptr<V>();
> }

> Unfortunately this relaxes the type system a bit, because it makes some
> explicit conversions implicit :-(

Not if the implementation of operator Ptr<V> uses only implicit
conversions...

Jason




Author: dlohmann@uhunix.uhcc.Hawaii.Edu (David Lohmann)
Date: Sat, 11 Jun 1994 10:24:31 GMT
Raw View
In article <TKORVOLA.94Jun3190725@dopey.hut.fi> Timo.Korvola@hut.fi writes:
>
>I have been working on a program that makes heavy use of polymorphism.
>In C++ this requires one to deal with objects by reference, passing
>around pointers or references instead of actual objects, which are
>often dynamically allocated.  Thus the shortcomings of the C++
>dynamic memory management have become a problem.
>
>Sometimes you need to store a copy of an object or a pointer to it
>somewhere, e.g. as part of another object.  You can't make a copy
>unless you know the exact type of the object (or use a rather ugly
>virtual function kluge), and copying large objects can cause
>significant and useless overhead.  But if you store a pointer and the
>original object gets deleted prematurely, you're in trouble.

[clip]

Maybe RTTI can provide a way of implementing some sort of
dynamic type-checking hack.  You could process the object
according to the type with a switch or something.  HIH.  :)

> Timo Korvola  Timo.Korvola@hut.fi


--
//-----------------------------------------------------------
// D. Peter Lohmann
//-----------------------------------------------------------





Author: rjl@f111.iassf.easams.com.au (Rohan LENARD)
Date: 22 Jun 1994 07:48:18 +1000
Raw View
In article <JASON.94Jun20232204@deneb.cygnus.com>,
Jason Merrill <jason@cygnus.com> wrote:
Jason>>>>>> Rohan LENARD <rjl@f111.iassf.easams.com.au> writes:
Jason>
Jason>> template <class T> Ptr {
Jason>> public:
Jason>>   template <class V> operator Ptr<V>();
Jason>> }
Jason>
Jason>> Unfortunately this relaxes the type system a bit, because it makes some
Jason>> explicit conversions implicit :-(
Jason>
Jason>Not if the implementation of operator Ptr<V> uses only implicit
Jason>conversions...

True, I hadn't though of that.

I expect though that users of this smart pointer class would get extremely
confused when they got an error like -

error at line xx in Ptr<T>::operator Ptr<V> -
  cannot convert T* to V*

when the error is actually caused by a piece of code somewhere else.


Regards,
 Rohan

--
------------------------------------------------------------------------
rjl@iassf.easams.com.au |
Rohan Lenard            |                .sig on holiday
+61-2-367-4555          |




Author: frederic@smartstar.com (Frederic in UNIX DEV)
Date: Thu, 9 Jun 94 17:57:58 GMT
Raw View
Timo Korvola (tkorvola@dopey.hut.fi) wrote:

[...]

: But polymorphism requires conversions between pointers, which I can't
: find a way to declare for these "smart pointers": For any class A and
: any public base class B of A it should be possible to implicitly
: convert a "smart pointer to A" to a "smart pointer to B".  The inverse
: conversion (B -> A) should also be possible (unless B is a virtual
: base class), but should not be done implicitly.

: I do not have access to the working papers, so I use the ARM as my
: reference.  It seems impossible to declare the conversions mentioned
: above, as e.g. a conversion from a "smart pointer to A" to a "smart
: pointer to B" would be a function template of two arguments (A and B),
: but it would have to be a member of "smart pointer to A" (if it were
: to be implemented as a cast) or "smart pointer to B" (if implemented
: as a constructor).  This would require nesting templates.

Recently I had to implement a similar class and came to the same conclusion
as you: smart pointers could be used interchangeably instead of regular
pointers except in the context of polymorphical manipulations.

I then switched compiler (from g++ to Sun CC 4.0), recompiled my files
and were surprised to notice that previously rejected files would now
compile correctly.

In particular:

    Ptr<A> pa; Ptr<B> pb;    // A is a base class of B
    pa = pb;

is rejected by g++ with an error message of the form:

    bad argument 1 for function `class Ptr<A> & Ptr<A>::operator =(class A *)'
   (type was class Ptr<B>)

whereas Sun CC4.0 merily accepts it.

It seems to me that the type inference mechanisms of the most recent compilers
are getting more powerful.

The code excerpts below illustrate how the various type conversion operations
are invoked in order to ensure inter-operability between regular pointers
and smart pointers.

I find it somewhat conspicuous that most articles that introduce the concept of
smart pointers do not cover the notion of polymorphism. Is it because it is
considered to be a non-problem or has the need for such polymorphical operations
never arised?

Do all the conversions described in the example below correspond to explicitly
stated rules of the (upcoming) C++ standard or are some compilers simply being
overzealous?

So far I know of two compilers that treat all the cases below correctly,
namely Sun CC 4.0 and Watcom C++. I definitely hope that their interpretation
of the type inference rules is correct and that it does indeed correspond to
the intent of the C++ language definition.

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Class Ptr<T> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// "Orthodox canonical class form" of a smart pointer.
// The following operators all need to be provided in some form or another
// in order to provide the ability to manipulate smart pointers as regular
// "built-in" pointers.

template <class T>
class Ptr {

public:

    T* operator->() const;
    operator T*() const;
    T& operator*() const;
    Ptr<T>& operator= (T* p);
    Ptr (const Ptr<T>& other);
    Ptr (T* p = 0);

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The example below illustrates that smart pointers behave exactly like
// regular pointers, even in the context of polymorphical assignments.

class A {};
class B: public A {};

int main () {

    A* a; Ptr<A> pa;    // Invokes constructor Ptr(A* p = 0)
    B* b; Ptr<B> pb;    // Invokes constructor Ptr(B* p = 0)

    a  = pa;            // Invokes `operator A*' of Ptr<A>.
                        // i.e. casting operator.

    a  = b;             // Regular polymorphical pointer assignment.

    a  = pb;            // Invokes `operator B*' of Ptr<B>.
                        // i.e. casting operator.

    pa = a;             // Invokes `operator=(A*) of Ptr<A>.
                        // i.e. assignment operator.

    pa = b;             // Invokes `operator=(A*) of Ptr<A>.
                        // i.e. assignment operator.

    pa = pb;            // Invokes `operator B*' of Ptr<B> _and_
                        // `operator=(A*) of Ptr<A>.
                        // i.e. casting and assignment operators.

// The following assignments violate the type rules and are correctly
// reported by the compiler.
//  b  = a;             // Error: Cannot assign A* to B*.
//  b  = pa;            // Error: Cannot assign Ptr<A> to B*.
//  pb = a;             // Error: Cannot cast from A* to Ptr<B>.
//  pb = pa;            // Error: Cannot cast from Ptr<A> to Ptr<B>.

   return 0;

 }

Frederic Deramat
frederic@smartstar.com




Author: frederic@smartstar.com (Frederic Deramat)
Date: Thu, 9 Jun 94 18:05:02 GMT
Raw View
Timo Korvola (tkorvola@dopey.hut.fi) wrote:

[...]

: But polymorphism requires conversions between pointers, which I can't
: find a way to declare for these "smart pointers": For any class A and
: any public base class B of A it should be possible to implicitly
: convert a "smart pointer to A" to a "smart pointer to B".  The inverse
: conversion (B -> A) should also be possible (unless B is a virtual
: base class), but should not be done implicitly.

: I do not have access to the working papers, so I use the ARM as my
: reference.  It seems impossible to declare the conversions mentioned
: above, as e.g. a conversion from a "smart pointer to A" to a "smart
: pointer to B" would be a function template of two arguments (A and B),
: but it would have to be a member of "smart pointer to A" (if it were
: to be implemented as a cast) or "smart pointer to B" (if implemented
: as a constructor).  This would require nesting templates.

Recently I had to implement a similar class and came to the same conclusion
as you: smart pointers could be used interchangeably instead of regular
pointers except in the context of polymorphical manipulations.

I then switched compiler (from g++ to Sun CC 4.0), recompiled my files
and were surprised to notice that previously rejected files would now
compile correctly.

In particular:

    Ptr<A> pa; Ptr<B> pb;    // A is a base class of B
    pa = pb;

is rejected by g++ with an error message of the form:

    bad argument 1 for function `class Ptr<A> & Ptr<A>::operator =(class A *)'
   (type was class Ptr<B>)

whereas Sun CC4.0 merily accepts it.

It seems to me that the type inference mechanisms of the most recent compilers
are getting more powerful.

The code excerpts below illustrate how the various type conversion operations
are invoked in order to ensure inter-operability between regular pointers
and smart pointers.

I find it somewhat conspicuous that most articles that introduce the concept of
smart pointers do not cover the notion of polymorphism. Is it because it is
considered to be a non-problem or has the need for such polymorphical operations
never arised?

Do all the conversions described in the example below correspond to explicitly
stated rules of the (upcoming) C++ standard or are some compilers simply being
overzealous?

So far I know of two compilers that treat all the cases below correctly,
namely Sun CC 4.0 and Watcom C++. I definitely hope that their interpretation
of the type inference rules is correct and that it does indeed correspond to
the intent of the C++ language definition.

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Class Ptr<T> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// "Orthodox canonical class form" of a smart pointer.
// The following operators all need to be provided in some form or another
// in order to provide the ability to manipulate smart pointers as regular
// "built-in" pointers.

template <class T>
class Ptr {

public:

    T* operator->() const;
    operator T*() const;
    T& operator*() const;
    Ptr<T>& operator= (T* p);
    Ptr (const Ptr<T>& other);
    Ptr (T* p = 0);

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The example below illustrates that smart pointers behave exactly like
// regular pointers, even in the context of polymorphical assignments.

class A {};
class B: public A {};

int main () {

    A* a; Ptr<A> pa;    // Invokes constructor Ptr(A* p = 0)
    B* b; Ptr<B> pb;    // Invokes constructor Ptr(B* p = 0)

    a  = pa;            // Invokes `operator A*' of Ptr<A>.
                        // i.e. casting operator.

    a  = b;             // Regular polymorphical pointer assignment.

    a  = pb;            // Invokes `operator B*' of Ptr<B>.
                        // i.e. casting operator.

    pa = a;             // Invokes `operator=(A*) of Ptr<A>.
                        // i.e. assignment operator.

    pa = b;             // Invokes `operator=(A*) of Ptr<A>.
                        // i.e. assignment operator.

    pa = pb;            // Invokes `operator B*' of Ptr<B> _and_
                        // `operator=(A*) of Ptr<A>.
                        // i.e. casting and assignment operators.

// The following assignments violate the type rules and are correctly
// reported by the compiler.
//  b  = a;             // Error: Cannot assign A* to B*.
//  b  = pa;            // Error: Cannot assign Ptr<A> to B*.
//  pb = a;             // Error: Cannot cast from A* to Ptr<B>.
//  pb = pa;            // Error: Cannot cast from Ptr<A> to Ptr<B>.

   return 0;

 }

Frederic Deramat
frederic@smartstar.com




Author: jason@cygnus.com (Jason Merrill)
Date: Thu, 9 Jun 1994 22:05:53 GMT
Raw View
>>>>> Frederic in UNIX DEV <frederic@smartstar.com> writes:

> In particular:

>     Ptr<A> pa; Ptr<B> pb;    // A is a base class of B
>     pa = pb;

> is rejected by g++ with an error message of the form:

>     bad argument 1 for function `class Ptr<A> & Ptr<A>::operator =(class A *)'
>    (type was class Ptr<B>)

This is a bug in g++.  It will be fixed in version 2.6.0.

Jason




Author: bs@alice.att.com (Bjarne Stroustrup)
Date: 6 Jun 94 14:46:16 GMT
Raw View

tkorvola@dopey.hut.fi (Timo Korvola @ Helsinki University of Technology) writes

 > I would like to know whether the standardization committee has
 > considered these problems or even decided on solutions that would
 > hopefully make implementing "smart pointers" easier.

The committee has accepted member templates which allows conversions
between different types generated from a template to be expressed.

See sec 15.9 in ``The Design and Evolution of C++'' for a discussion
of such techniques.

 - Bjarne




Author: pjl@graceland.att.com (Paul J. Lucas)
Date: Mon, 6 Jun 1994 18:36:02 GMT
Raw View
In <28017@alice.att.com> bs@alice.att.com (Bjarne Stroustrup) writes:

>tkorvola@dopey.hut.fi (Timo Korvola @ Helsinki University of Technology) writes

> > I would like to know whether the standardization committee has
> > considered these problems or even decided on solutions that would
> > hopefully make implementing "smart pointers" easier.

>The committee has accepted member templates which allows conversions
>between different types generated from a template to be expressed.

 Also my humble operator-> proposal which has been accepted.
--
 - Paul J. Lucas
   AT&T Bell Laboratories
   Naperville, IL




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Mon, 6 Jun 1994 17:14:12 GMT
Raw View
In article <JASON.94Jun3131842@deneb.cygnus.com> jason@cygnus.com (Jason Merrill) writes:
>>>>>> Timo Korvola <tkorvola@dopey.hut.fi> writes:
>
>> But polymorphism requires conversions between pointers, which I can't
>> find a way to declare for these "smart pointers": For any class A and
>> any public base class B of A it should be possible to implicitly
>> convert a "smart pointer to A" to a "smart pointer to B".  The inverse
>> conversion (B -> A) should also be possible (unless B is a virtual
>> base class), but should not be done implicitly.
>
>> This would require nesting templates.
>
>This is now allowed for non-static member functions.  For example:
>
>template <class T>
>class Ptr
>{
>  T * p;
> public:
>  Ptr (T * np): p (np) { }
>  template <class U> operator Ptr<U> () { return p; }
>  template <class U> Ptr<U> convert () { return (U*)p; }
>  operator T* () { return p; }
>};

 Unfortunately, member templated operator conversions --
one of the main reasons for having them -- are mutually exclusive with
overloading.

 f(A);
 f(B);
 Ptr<T> x;
 f(x);

Which 'f' is called?

I suggested member templates had to be inlined and invalid
instantiations not considered for overload resolution -- and my
proposal was slammed on 5 different grounds. (Literally,
Erwin Unruh tore it apart).

That means member templates are useful for just about everything
EXCEPT user defined operator conversions :-(
(Or, they're an alternative to overloaded functions taking
that class as an argument -- cant figure out which ;-)


--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: tkorvola@dopey.hut.fi (Timo Korvola)
Date: 03 Jun 1994 16:07:25 GMT
Raw View
I have been working on a program that makes heavy use of polymorphism.
In C++ this requires one to deal with objects by reference, passing
around pointers or references instead of actual objects, which are
often dynamically allocated.  Thus the shortcomings of the C++
dynamic memory management have become a problem.

Sometimes you need to store a copy of an object or a pointer to it
somewhere, e.g. as part of another object.  You can't make a copy
unless you know the exact type of the object (or use a rather ugly
virtual function kluge), and copying large objects can cause
significant and useless overhead.  But if you store a pointer and the
original object gets deleted prematurely, you're in trouble.

Implementing a reference counting scheme would make things easier (I
see no feasible way to implement true garbage collection).  The
natural way of doing this, it seems to me, would involve creating a
class template to be used instead of pointers (the template argument
being the type of object pointed to).

These "smart pointers" would contain the address of a reference
counter as well as the address of the referenced object.  Overloaded
versions of unary * and -> would provide dereferencing and
copy constructors, destructors and assignment would maintain the
reference counter and delete the object when it becomes unreferenced.

But polymorphism requires conversions between pointers, which I can't
find a way to declare for these "smart pointers": For any class A and
any public base class B of A it should be possible to implicitly
convert a "smart pointer to A" to a "smart pointer to B".  The inverse
conversion (B -> A) should also be possible (unless B is a virtual
base class), but should not be done implicitly.

I do not have access to the working papers, so I use the ARM as my
reference.  It seems impossible to declare the conversions mentioned
above, as e.g. a conversion from a "smart pointer to A" to a "smart
pointer to B" would be a function template of two arguments (A and B),
but it would have to be a member of "smart pointer to A" (if it were
to be implemented as a cast) or "smart pointer to B" (if implemented
as a constructor).  This would require nesting templates.

Furthermore it appears impossible to control whether such conversions
would be applied implicitly, and there is no way to impose
restrictions on template arguments that would e.g. require an
argument to be a base class of some specified class.

I would like to know whether the standardization committee has
considered these problems or even decided on solutions that would
hopefully make implementing "smart pointers" easier.  Following the
ARM the only way seems to be an ugly preprocessor hack, as in the days
before templates.
--
 Timo Korvola  Timo.Korvola@hut.fi




Author: jason@cygnus.com (Jason Merrill)
Date: Fri, 3 Jun 1994 20:18:41 GMT
Raw View
>>>>> Timo Korvola <tkorvola@dopey.hut.fi> writes:

> But polymorphism requires conversions between pointers, which I can't
> find a way to declare for these "smart pointers": For any class A and
> any public base class B of A it should be possible to implicitly
> convert a "smart pointer to A" to a "smart pointer to B".  The inverse
> conversion (B -> A) should also be possible (unless B is a virtual
> base class), but should not be done implicitly.

> This would require nesting templates.

This is now allowed for non-static member functions.  For example:

template <class T>
class Ptr
{
  T * p;
 public:
  Ptr (T * np): p (np) { }
  template <class U> operator Ptr<U> () { return p; }
  template <class U> Ptr<U> convert () { return (U*)p; }
  operator T* () { return p; }
};

struct A { };
struct B: public A { };

Ptr<A> ap = new A;
Ptr<B> bp = ap.convert<B>();  // force downcast
ap = bp;    // implicit upcast

> Furthermore it appears impossible to control whether such conversions
> would be applied implicitly and there is no way to impose restrictions on
> template arguments that would e.g. require an argument to be a base class
> of some specified class.

See my sample implementation above:  The implicit conversions allowed by
the language will be applied implicitly, and those that require a cast will
require you to call convert<U>().  Of course, you'll need to wait for your
compiler to support both method templates and specialization syntax for
templates...

Jason