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