Resource management (computing): Difference between revisions

Content deleted Content added
merge RAII discussion, clarify what is OO
Object-oriented programming: elab, integrate RAII (make it less RAII-focused), and contrast owning vs. viewing
Line 62:
 
=== Object-oriented programming ===
In [[object-oriented programming]], manyresources garbage-collectedare languages,encapsulated includingwithin [[Javaobjects (programmingthat language)|Java]]use them, [[Csuch Sharpas (programminga language)|C#]]<code>file</code> andobject having a [[PythonField (programmingcomputer languagescience)|Pythonfield]], supportwhose variousvalue formsis of thea [[disposefile patterndescriptor]] to(or simplifymore cleanupgeneral of[[file resourceshandle]]). This resultsallows inthe simplerobject codeto use and canmanage servethe asresource anwithout alternativeusers of the object needing to RAIIdo forso. "shallow"However, there is a wide variety of ways that objects and resources can be related.
 
Firstly, there is the question of ownership: does an object ''have'' a resource?
In C++, the usual approach to resource management is [[Resource Acquisition Is Initialization]] (RAII), which ties resource management to the lifetime of an object. This has the advantage that it can also be used for heap-managed resources.
* Objects can ''own'' resources (via [[object composition]], a strong "has a" relationship).
* Objects can ''view'' resources (via [[object aggregation]], a weak "has a" relationship).
* Objects can ''communicate'' with other objects that have resources (via [[Association (object-oriented programming)|Association]]).
 
Objects that have a resource can acquire and release it in different ways, at different points during the [[object lifetime]]; these occur in pairs, but in practice they are often not used symmetrically (see below):
The compositional properties of RAII, however, differ significantly from scope bound resources in that it allows for full [[Encapsulation (object-oriented programming)|encapsulation]] of the resources behind an abstraction. With the dispose pattern for resource management, "being a resource" becomes a property that is [[transitive relation|transitive]] to composition. That is, any object that is composed using a resource that requires resource management effectively itself becomes a resource that requires resource management.
* Acquire/release while the object is valid, via (instance) methods such as <code>open</code> or <code>dispose</code>.
* Acquire/release during object creation/destruction (in the initializer and finalizer).
* Neither acquire nor release the resource, instead simply having a ''view'' or ''reference'' to a resource managed externally to the object, as in [[dependency injection]]; concretely, an object that has a resource (or can communicate with one that does) is passed in as an argument to a method or constructor.
 
Most common is to acquire a resource during object creation, and then explicitly release it via an instance method, commonly called <code>dispose</code>. This is analogous to traditional file management (acquire during <code>open</code>, release by explicit <code>close</code>), and is known as the [[dispose pattern]]. This is the basic approach used in several major modern object-oriented languages, including [[Java (programming language)|Java]], [[C Sharp (programming language)|C#]] and [[Python (programming language)|Python]], and these languages have additional constructs to automate resource management. However, even in these languages, more complex object relationships result in more complex resource management, as discussed below.
This limitation is typically encountered whenever developing custom classes. Custom classes in C# and Java have to explicitly implement the <code>dispose</code> method in order to be dispose-compatible for the client code. The <code>dispose</code> method has to contain explicit closing of all child resources belonging to the class. This limitation does not exist in C++ with RAII, where the destructor of custom classes automatically destructs all child resources recursively without requiring any explicit code.
 
==== RAII ===
{{main|Resource Acquisition Is Initialization}}
 
A natural approach is to make holding a resource be a [[class invariant]]: resources are acquired during object creation (specifically initialization), and released during object destruction (specifically finalization). This is known as [[Resource Acquisition Is Initialization]] (RAII), and ties resource management to [[object lifetime]], ensuring that live objects have all necessary resources. Other approaches do not make holding the resource a class invariant, and thus objects may not have necessary resources (because they've not been acquired yet, have already been released, or are being managed externally), resulting in errors such as trying to read from a closed file. This approach ties resource management to memory management (specifically object management), so if there are no memory leaks (no object leaks), there are no [[resource leak]]s. RAII works naturally for heap-managed resources, not only stack-managed resources, and is composable: resources held by objects in arbitrarily complicated relationships (a complicated [[object graph]]) are released transparently simply by object destruction (so long as this is done properly!).
 
RAII is the standard resource management approach in C++, but is little-used outside C++, despite its appeal, because it works poorly with modern automatic memory management, specifically [[tracing garbage collection]]: RAII ''ties'' resource management to memory management, but these have significant differences. Firstly, because resources are expensive, it is desirable to release them promptly, so objects holding resources should be destroyed as soon as they become garbage (are no longer in use). Object destruction is prompt in deterministic memory management, such as in C++ (stack-allocated objects are destroyed on stack unwind, heap-allocated objects are destroyed manually via calling <code>delete</code> or automatically using <code>unique_ptr</code>) or in deterministic reference-counting (where objects are destroyed immediately when their reference count falls to 0), and thus RAII works well in these situations. However, most modern automatic memory management is non-deterministic, making no guarantees that objects will be destroyed promptly or even at all! This is because it is cheaper to leave some garbage allocated than to precisely collect each object immediately on its becoming garbage. Secondly, releasing resources during object destruction means that an object must have a ''[[finalizer]]'' (in deterministic memory management known as a ''destructor'') – the object cannot simply be deallocated – which significantly complicates and slows garbage collection.
 
==== Complex relationships ====
When multiple objects rely on a single resource, resource management can be complicated.
 
A fundamental question is whether a "has a" relationship is one of ''owning'' another object ([[object composition]]), or ''viewing'' another object ([[object aggregation]]). A common case is when one two objects are chained, as in [[pipe and filter]] pattern, the [[decorator pattern]], or the [[adapter pattern]]. If the second object (which is not used directly) holds a resource, is the first object (which is used directly) responsible for managing the resource? This is generally answered identically to whether the first object ''owns'' the second object: if so, then the owning object is also responsible for resource management ("having a resource" is [[transitive relation|transitive]]), while if not, then it is not. Further, a single object may "have" several other objects, owning some and viewing others.
 
Implementation-wise, in object composition, if using the dispose pattern, the owning object thus will also have a <code>dispose</code> method, which in turn calls the <code>dispose</code> methods of owned objects that must be disposed; in RAII this is handled automatically (so long as owned objects are themselves automatically destroyed: in C++ if they are a value or a <code>unique_ptr</code>, but not a raw pointer: see [[pointer ownership]]). In object aggregation, nothing needs to be done by the viewing object, as it is not responsible for the resource.
 
Both are commonly found. For example, in Java, <code>[https://docs.oracle.com/javase/8/docs/api/java/io/Reader.html#close-- Reader#close()]</code> closes the underlying stream, and these can be chained. For example, a <code>[https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html BufferedReader]</code> may contain a <code>[https://docs.oracle.com/javase/8/docs/api/java/io/InputStreamReader.html InputStreamReader]</code>, which in turn contains a <code>[https://docs.oracle.com/javase/8/docs/api/java/io/FileInputStream.html FileInputStream]</code>, which in turn contains a <code>[https://docs.oracle.com/javase/8/docs/api/java/io/File.html File]</code>, and calling <code>close</code> on the <code>BufferedReader</code> in turn closes the <code>InputStreamReader</code>, which in turn closes the <code>FileInputStream</code>, which in turn closes the <code>File</code>, which in turn releases the system file resource.
 
By contrast, in Python, a [https://docs.python.org/2/library/csv.html#csv.reader csv.reader] does not own the <code>file</code> that it is reading, so there is no need (and it is not possible) to close the reader, and instead the <code>file</code> itself must be closed.<ref>[http://stackoverflow.com/questions/3216954/python-no-csv-close Python: No csv.close()?]</ref>
 
In case of a more complicated [[object graph]], such as multiple objects sharing a resource, or cycles between objects that hold resources, proper resource management can be quite complicated, and exactly the same issues arise as in object finalization (via destructors or finalizers).
 
=== Structured programming ===