‹header›
‹date/time›
Click to edit Master text styles
Second level
Third level
Fourth level
Fifth level
‹footer›
‹#›
What exactly is bad about this code? The fact that the programmer has to work hard:
1.When creating it, remember to always match the acquisition and release calls.
2.When reviewing, check for the matching calls
3.Think non-locally. The release might be far away from the acquisition
4.Make sure that nothing non-linear (a fork in the execution) happens between the two calls
Non-locality and non-linearity are the major problems faced by programmers. The infamous gotos were banished exactly for this reason. Structured programming linearized and localized procedural programming. RM does the same for resources.
1.
Simple example: a pointer points to a resource.
Is the pointer valid?
Should this pointer be deleted?
Do all execution paths lead to the deletion?
Are other pointers pointing to the same resource?
Do they delete it?
Do any paths of execution lead to double deletion?
Introducing the smallest flow of control modification, arbitrarily far from pointer manipulations, might lead to the resource leaking or double deletion.
This is how you can write “exception safe” code in C.
This code is not readable—a lot of bookkeeping. It is also incorrect. Consider what happens when an exception is thrown between the two mallocs. The freeing of p2 will result in unexpected behavior. Correct code should start with:
char * p1 = 0;
char * p2 = 0;
A lot of programmers who switch from C to C++ write this kind of unmaintainable code. It is even worse than the original C, because there is no analog of “finally” in C++.
This is how you encapsulate the critical section. This class should be part of your library. The correctness of resource management in this class is pretty obvious to a C++ programmer. It’s a no-brainer.
Again, a good candidate for your library. Proof of correctness, a no-brainer.
Compare this with previous “exception safe” attempts. This is an exception safe no-brainer. The code after the lock is constructed can be modified in crazy ways: returns, gotos, exceptions. Nothing will break its “resource correctness.”
Normally the critical section object is embedded in some higher-level object that it protects.
An object (instance of a class) is a resource, so are its sub-objects and its base class sub-objects, and so on, recursively.
Notice that Memory is the only resource that is managed by generic garbage collectors. Heap objects are sort-of managed, in the sense that their eventual destruction is guaranteed.
Progress UI is an interesting case. An object that switches mouse cursor to hourglass in its constructor and switches it back in its destructor. Gives you a guarantee that after the command is executed (successfully or not), the cursor will go back to normal. Similar solutions apply to progress bars, “busy” displays in the status bar, etc.
A scope owns all its local variables (including function arguments within the scope of the function). An object owns its member data, which may be objects themselves, recursively.
These two types of ownership guarantee resource release:
When a scope is exited, all local variables are destroyed. Stack unwinding make this process exception safe When an object is destroyed, all its data members (and the base class part) is destroyed. Note: We are assuming that no sane C++ programmer writes classes whose destructors may throw exceptions.
Every resource is owned by an object. Notice the locality of the proof of correctness: it’s enough to check the constructors and the destructors (which usually are defined next to each other).
Normally this is done using auto_ptrs.
According to the First Rule of Acquisition, a resource is always allocated in a constructor of an object whose destructor deallocates this resource. So all resources are encapsulated in objects. These objects can live in local scopes, global scopes, or be sub-objects of other objects. Their destruction is guaranteed. If an object is allocated from the heap, its destruction is also guaranteed, because it can only live inside its owner object.
Looking at code written using the RM precepts, you can easily prove its resource correctness. Just check that all resources are allocated in constructors, and that the corresponding destructors deallocate them. That’s all.
These rules will now be relaxed without invalidating our argument.
Very few programs can be written without resources being transferred between scopes.
As before, let’s look at built-in mechanisms first.
Objects are resources, how are they transferred?
Return by value transfers an object from function scope to the caller’s scope.
Passing by value transfers an object (non-destructively) in the opposite direction. Both of these use a combination of the copy constructor and operator=. Those can be overridden, giving room for different transfer policies.
Passing “by value” between scopes. The resource to be passed is encapsulated in a lightweight transfer object with value-like semantics. By defining the appropriate copy constructor and by overriding operator=, the transfer object can implement various ownership transfer policies.
By declaring these two private, you can disallow transfer.
Move semantics, a la auto_ptr.
A small subset of RM dealing with heap resources makes garbage collection unnecessary. Realistically, auto_ptr provides adequate transfer policy. Since auto_ptr is a template, we can’t really follow the First Rule of Acquisition. Instead of allocating the resource in its constructor, we allocate it
 as an expression passed directly to auto_ptr constructor
 as an expression that is the argument to reset
To transfer the ownership we pass auto_ptr by value to and from functions.
Note: Don’t pass auto_ptr by reference—it’s confusing. If you don’t want to pass ownership, get the pointer from auto_ptr and pass it around.
auto_ptr<Foo> foo (new Foo);
UseFoo (foo.get ()); // no ownership passed
The standarizers took great pains to prevent the possibility of using auto_ptr as template parameters to standard containers. Move semantics could easily break a standard container, which thinks nothing of making temporary copies.
Reference counted objects are ok with standard containers. For instance, Alexandrescu’s smart pointers or Boost smart pointers will work correctly.
Notice that syntactically a container of smart pointers looks like a container of pointers, so you have to get used to it. For instance, when you dereference an iterator, you get a pointer. You have to use special pointer-accepting predicates for searching and other standard algorithms.
The biggest inconvenience of this approach is that you essentially have to use ref-counted pointers everywhere you would normally use auto_ptrs. What if you are a fan of auto_ptrs? They are smaller, faster, and cooler.
To store heap object use my auto_vector.
What if you need an auto_set or some such? You can write one, or you can separate ownership from set-ness. Keep heap objects in auto_vector and store non-owning pointers to them in a standard set.
There is an example of this on our web site www.relisoft.com/resource. It implements topological sort using this approach.
Push_back doesn’t strictly follow exception-safety requirements. If it fails, it still modifies its argument (deletes the pointer). I would argue that this is very reasonable behavior in the case of auto_ptrs. They are supposed to release their resource in the case of failure. Pop_back syntax differs from the ususal pop_back syntax, which is not supposed to return anything. Again, this makes perfect sense for auto_ptrs. The (rather unconventional) division of responsibilities between pop_back() and back() in standard containers is done in the name of exception safety. Here, instead, the conventional pop syntax is exception safe. Incidentally, auto_vector::back() returns a pointer, not auto_ptr.
This is a clever trick to allow index access to auto_vector to be used as an lvalue (the left-hand side of the assignment). Ownership of the new object has to be transferred to auto_vector. The old entry should be released in the process. This is done by overloading operator[] to return a helper object that serves as an lvalue
This object keeps a reference to a pointer, so it can not only modify (in this case, delete) the object pointed to, but also modify the pointer itself. Notice that auto_lvalue has pointer-like behavior. This, for instance, makes such code possible:
autoVec [1]->Method ();
You may also want to prevent the incorrect use
delete autoVec [1];
by overriding operator delete.
Following these few principles you get “controlled” garbage collection. You not only have the guarantee of destruction, but you also know when the destruction takes place.
Completely, utterly useless. Not only that, languages with GC usually don’t offer other mechanism that can be used to implement RM (Java, C#), so you are back to using the ugly try/catch type RM.
Windows uses many types of handles. We encapsulate all of them in this template. NativeHandle is the original Windows type, like HWND, HDC, HPEN, etc. Note that Handle is-a NativeHandle not by way of inheritance, but through the overloading of the cast operator. Use this trick when the underlying type is not a struct or class (here NativeHandle is a pointer to struct). This generic handle is not a resource yet—it corresponds to a naked pointer in the auto_ptr lingo.
This is the analog of auto_ptr. AutoHandle is-a BaseHandle. BaseHandle can be the generic Handle, or it can be derived from Handle, adding specific functionality (examples later). Different types of handles are disposed of differently. We encapsulate it inside DisposalPolicy, a la Alexandrescu.
These two member functions define AutoHandle’s move semantics. Transfer policy is similar to that of auto_ptr—there’s always only one owner. There are additional subtleties that are needed for return by value to work. A helper (template) class auto_handle_ref is used the same way auto_ref is used with auto_ptr.
This is the default DisposalPolicy. It calls the specific Dispose method that has to be defined for each BaseHandle by specializing this template.
Simple example. BaseHandle is a typedef of generic Handle<HICON>. Disposal policy is specialized for this particular type.
This is a more involved example. Gdi::Handle is a generic GDI handle. All GDI handles are disposed using the same API DeleteObject.
HPEN is a specific example of a GDI handle. Pen::Handle is-a Win::Handle<HPEN>. Pen::AutoHandle uses the GDI disposal policy, common for all GDI objects (brushes, bitmaps, etc.).
This is an example of how Pen::AutoHandle is used to manage the pen resource. The Pen::Maker object gathers data needed for the creation of a pen (there are more options than just width). The Create call allocates the resource, puts it immediately inside an owner/transfer object and returns it by value. The caller becomes the owner. Notice that the caller is forced to use a Pen::AutoHandle to receive the result of Create. There is no chance of leaking the pen at this point.
This is a different example of RM. The base class Canvas encapsulates a handle to a device context. Its member functions encapsulate all the APIs that take HDC as their main argument.
This is an analog of a naked pointer.
There are several owner classes with different acquisition/release methods. This one is created in the context of WM_PAINT message. Notice the no-transfer policy (private copy-co and assignment).
This one is created outside WM_PAINT to update a window.
Each call to new must immediately pass its result to auto_ptr.
There should be no explicit calls to delete.
Every class with virtual member functions must have a virtual destructor.