trug Userís Guide

Related Documents

Overview

A trug is a general container. It holds something else. The term trug can be used to refer to any concrete implementation of the proper interface (see hypothetical_trug), and the actual template named trug has a few supplied specializations.

A trug of a pointer type, e.g. trug<int&>, provides strict ownership semantics and automatic destruction for pointers, similarly to the standard std::auto_ptr class.

A trug of a Windows HANDLE type provides strict ownership and automatic destruction in an analogous way to the pointers. For example, trug<HKEY> knows to call the Win32 API function RegCloseKey when “freed”.

Contents

Threading and Housekeeping Issues

The supplied trug classes donít have any particular thread safety built in. The various members should be serialized, but may be called from any thread as long as only one member is called at a time.

The “housekeeping” is what this class is all about. The point of using a trug is to impose automatic housekeeping management and/or error checking on the contained object.

A trug may not be copied directly. So, its copy constructor and copy assignment operator are disabled. However, the trug will give the sense of copying (in particular, returning a trug from a function) by using the matching trule class.

Purpose and Concept

A trug is a generalized container. The purpose of a trug is to hold something else. The reasons for putting such a wrapper around something include abstracting an indirect storage mechanism and imposing houskeeping semantics.

The name trug comes from the English gardening basket (for examples, see Royal Sussex Trug store). Lately I've seen it used for any kind of general-purpose container, such as these that have nothing to do with the traditional shape and construction. This templateís purpose as a handy often-used container lives up to its namesake.

What Do I Do With It?

Using Pointers

Consider the following examples. The trug is being used to hold a primitive pointer, and you can see it gives the same benefits as the std::auto_ptr.

typedef array_trug<int*> atp;

atp::trule foo (int size)
 {
 atp p1 (new int [size]);
 do_stuff_1();
 atp p2 (new int [size*2]);
 do_stuff_2();
 return p1;
 }

// later…
atp p3= foo (x);

Youíll notice that p1, p2, and p3 are defined to be of type atp rather than type int*. Here is what that does for us.

First of all, notice that p2 is never explicitly deleted. Like with any normal self-respecting object type, it takes care of itself and knows to delete the pointer when it goes out of scope.

That may be a handy bit of help that avoids a little typing and some effort to pay attention to the needs, but the benefit is more serious than that. What happens if the function throws an exception? If an exception is thrown during do_stuff_1, then it will automatically delete p1 when unwinding the stack. If the exception is thrown during do_stuff_1, then it knows to delete both p1 and p2. Meanwhile, if the function runs to completion it knows to delete p2 but leave p1 to be transfered to the caller! Making this function exception-safe with two ordinary pointers would require a lot of care and quite a few more lines, wrapping the whole thing in a try block and moving the definitions of the variables away from their point of first use. Simply giving them destructors is so much easier! It makes them work the way stuff ought to work in C++.

Meanwhile, the caller knows that foo will return a pointer and that the caller takes responsibility for the pointer returned. Such responsibilities are often under-documented, while here the compiler enforces it. This is explored further in the next example.

typedef array_trug<int*> atp;

void f1 (atp param1);  // wrong!
void f2 (const atp& param2);
void f3 (int* param4);

// later…
atp p1 (new int [42]);

f1 (p1);  // compile-time error!
f2 (p1);
f3 (p1);  // compile-time error!
f3 (p1.eject());
f3 (p1.get_r());
f3 (p1.duplicate());

Calling f1(p1) it is a compile-time error. By design, trugs cannot be passed by value. The problem with “shallow value” objects is that copying them causes ownership problems, so you cannot (simply) copy it.

Calling f2(p1) shows a clearer, and correct, case. Passing p1 by reference makes it clear that the function is not going to take ownership of the pointer.

Calling f3(p1) is a compile-time error. You cannot (simply) pass a trug to a function taking a raw pointer. Having the trug indicates that the lifetime and ownership of the pointer is under its control, and it will not implicitly give up that control. Is f3 going to take ownership of the pointer or not? Youíll have to check the documentation of the function to know; it is certainly not known to the compiler.

On the other hand, calling f3 (p1.eject()) is legal, because you are being explicit about what the ownership semantics are. You are saying that p1 is to give up ownership (which also sets p1 to null) when it passes the raw pointer to f3.

Make it null-out p1 only if p1 doesn't throw an exception!

Alternativly, calling f3 (p1.get_r()) indicates that p1 will not give up ownership. So, you canít simply use a trug where a raw pointer is expected, but you can use the members eject or get_r to explicitly indicate how it should work.

Another option is to call f3 (p1.duplicate()). This will cause a deep-copy, and pass the copy (a different pointer value) to the function.

typedef array_trug<int*> atp;

atp::trule foo ()
 {
 atp p1 (new int [42]);
 return p1;
 }

int* bar()
 {
 int* p2= new int [42];
 return p2;
 }

// later…
atp p3= foo ();
p3= bar();

Using trugs for returning things is a little more difficult. Not difficult to do the calling and returning, but only in how to declare the return type of the function. Unlike the std::auto_ptr class which mixes up the meaning of “copy”, the ability to transfer (not “copy”) the value out of the function is done using a helper class, trule.

All you have to do is declare the return type, not of the trug (in this case atp), but of the nested type trule. Whatever the trug type is, just append ::trule to it when declaring the return type of a function that you want to return the trug.

By doing so, the trule prevents the copy constructor of the trug from being triggered, which would be a problem because copying trugs is disallowed. In the call atp p3= foo(), the constructor of p3 is getting the returning p1 value wrapped in a trule, rather than the value p1 itself. The trule mediates the transfer of the value from one trug to the other, so that p1ís destructor will not free the pointer and p3 takes ownership.

Itís also no problem for a trug to be assigned the result of bar, which is a raw pointer. In this case, p3 takes ownership, and frees the previous pointer it was holding. Note that when initializing, you must use the initialization syntax rather than the = syntax because the constructor is explicit.



The reason the constructor is declared explicit is to prevent the mistake illustrated in the listing to the right.

Using HANDLEs

The trug isnít only for pointers.

the hypothetical_trug class

specialized trug for pointers

specialized trug for Windows HANDLEs