[comp.lang.c++] A Parameterized Class for Semi-automatic Memory Management

bothner@sevenlayer.cs.wisc.edu (Per Bothner) (09/23/90)

Many applications manage memory using a convention
that certain references are the only existing ones
to the referents. That is, no other pointer can
legally reference that object. We can say that
the (implicit) "reference count" of the object is one.

Let us say that such a reference (pointer) is 'owned'.
We also say that the owned reference points to an owned object.
Non-automatic storage management depends on keeping
track of which pointers are owned.

Examples:
* If a function returns an owned pointer/reference,
the caller is reponsible for deleting the returned object.
* If a class field is owned, the object it points to
should be deleted by the class's destructor.
* If a formal parameter is owned, the called function
must delete it before returning. Alternatively, it can
"give" away ownership.

It is easily to lose track of which pointers are
owned and which are not. The following idea makes
it easy to keep track of this, and can to some extent
automate the job. (The idea is inspired by an idea
of Charles Haynes, of Digital Equipment Corporation's
Western Software Laboratory.)

THE SOLUTION

We define a new standard parameterized class:

template<class T> class owned {
  private:
    T* object;
  public:
    owned(T* ptr) { object = ptr; } // Normal contructor
    owned(owned<T>& X) { object = X.object; X.clear(); } // Steal ownership
    clear() { object = NULL; } // Remove ownership (and reference)
    T* operator->() { return object; } // Get actual pointer
    ~owned() { delete object; } // Desctructor deletes object
};

The clear() method can be used when automatic deletion of object is
to be inhibited, perhaps because the object has been "given"
to some other owner.

EXAMPLES OF USE

* A function that returns an owned object:

owned<Foo> F(...)
{
    owned<Foo> myfoo(new Foo(...));
    return myfoo;
}

* Passing an owned object as a parameter:

void G(owned<Foo> myfoo)
{
    myfoo->do_something();
    // object is automatically deleted on exit.
}

void H()
{
    owned<Foo> myfoo = ...;
    G(myfoo); // gives ownership away to G
    // At this point myfoo is invalid.
    // Its pointer is NULL, so the (old) owned object won't be deleted twice
}

* An owned field:

struct Bar {
  public:
    owned<Foo> myfoo;
    Bar(...) : myfoo(new Foo(...)) { ...}
    print() { ... myfoo->print(); ... }
};

Note that Bar::myfoo is automatically deleted when a Bar object
is destroyed. Bar::print shows how the (Foo) object can be
accessed without giving away ownership.

* Re-using an owned parameter:

owned<BigNum> operator+(owned<BigNum> x, owned<BigNum> y)
{
    x += y;
    return x;
    // The y object gets deleted, but the x object is re-used for the sum
}

REFERENCES INSTEAD OF POINTERS

Since an owned<T> "owns" the object itself, not just a pointer
to it, it would be preferable sense to think of an owned<T>
as an encapsulated T&, rather than an encapsulated T*.
Such a definition would look like the following,
except it would not be allowed, since C++ currently lacks
'operator.'. (There seems to be some sentiment for adding
operator. to the language definitions; consider this note
as support for these efforts.)

template<class T> class owned {
  private:
    T* object;
  public:
    // Copy constructor steals object.
    clear() { object = NULL; }
    owned(owned<T>& X) { object = X.object; X.clear(); }
    owned(T& ref) { object = &ref; }
    T& operator.() { return *T; } // NOT ALLOWED BY CURRENT C++
    ~owned() { delete object; }
};
--
	--Per Bothner
bothner@cs.wisc.edu Computer Sciences Dept, U. of Wisconsin-Madison

thomasw@hpcupt1.HP.COM (Thomas Wang) (09/24/90)

In your scheme, is shared ownership prohibited?

If so, do you think this is too much a restriction?


 -Thomas Wang
              (Everything is an object.)
                                                     wang@hpdmsjlm.cup.hp.com
                                                     thomasw@hpcupt1.cup.hp.com

jimad@microsoft.UUCP (Jim ADCOCK) (09/25/90)

This proposal fixes the smaller problem of keeping track of ownership of 
objects, while ignoring the larger problem:

On each parameter passed to a method, or returned from a method, the class
producer and class consumer must agree on whether ownership should be passed
for that parameter or not.  If producer and consumer don't agree on whether
a parameter should pass ownership or not, then the class is not usable.
Most classes have a very large number or parameters that could pass ownership
or not, thus it seems unlikely that class producers and consumers would agree
on all those parameters.  Thus classes would not be generally reuasable.

Thus one is lead to consider schemes by which class produces and class users
are able to "share" "ownership" of objects.

bothner@sevenlayer.cs.wisc.edu (Per Bothner) (09/28/90)

>In your scheme, is shared ownership prohibited?

If an object is owned, it is not supposed to be shared.
It is possible to "lend" an object, with the understanding
it must not be destroyed. But the whole point of the "owned"
template class is that it specifies that an object is NOT shared.

>If so, do you think this is too much a restriction?

In general, yes, but the scheme is intended as a discipline,
not a universal solution. Note that "owned" objects can
coexist with other storage management schemes, such as
garbage collection.

An example: Variable-sized objects (e.g. arbitrary-precision
Integers) in libg++ are all implicitly "owned", since libg++
copies objects as needed to guarantee that only one reference
exists to each object. This is done semi-automatically, and
copying/deleting happens very much like in my "owned" scheme.
But the problem is that *all* Integers in libg++ are "owned":
if you want to share two Integers (and suppress the automatic
copying), you have to to muck about with casts and the
low-level representation. It would be nice to have two kind of
Integer class: one whose objects are owned, and another class 
whose objects can be shared (at the cost of requiring the programmer
(or a garbage collector) to explicitly reclaim storage). The
"owned" scheme is a way to abstract away this distinction:
The library designer need just write the "shared" version
of (say) Integer, and then to get the "owned" semantics
(as in the current libg++) one just declares
one's variables as "owned<Integer>".

A similar scheme for reference counting (e.g. reference_counted<T>)
would also be worth exploring, but that is another discussion.
-- 
	--Per Bothner
bothner@cs.wisc.edu Computer Sciences Dept, U. of Wisconsin-Madison

bothner@sevenlayer.cs.wisc.edu (Per Bothner) (09/28/90)

>On each parameter passed to a method, or returned from a method, the class
>producer and class consumer must agree on whether ownership should be passed
>for that parameter or not.

Good point - but they have to do that anyway. My proposal
does have the advantage that the compiler enforces agreement.

It may be possible to extend the scheme to automatically convert
between owned and shared references:

- If an owned pointer is initialized *from* a shared pointer,
the initializer should allocate and contruct a copy.
- An owned pointer can be coerced *to* a shared pointer
by just extracting the stored (plain) pointer.

One problem: there are now two kinds of initializtion of
owned variables: From (a pointer to) a shared object (yielding
ownership of a copy), and from (a pointer to) an owned object
(transferring ownership).

The former could be expressed by owned(T*) and the latter
by owned(T&). I don't know how error-prone this would be.

It may also be possible to generate better code
if knowledge about "owned" is built into the compiler.
-- 
	--Per Bothner
bothner@cs.wisc.edu Computer Sciences Dept, U. of Wisconsin-Madison