Item 15: Provide access to raw resources in resource-managing classes.

Resource-managing classes are wonderful. They're your bulwark against resource leaks, the absence of such leaks being a fundamental characteristic of well-designed systems. In a perfect world, you'd rely on such classes for all your interactions with resources, never sullying your hands with direct access to raw resources. But the world is not perfect. Many APIs refer to resources directly, so unless you plan to foreswear use of such APIs (something that's rarely practical), you'll have to bypass resource-managing objects and deal with raw resources from time to time.

For example, Item 13 introduces the idea of using smart pointers like auto_ptr or TR1::shared_ptr to hold the result of a call to a factory function like createInvestment:

 std::tr1::shared_ptr<Investment> pInv(createInvestment());  // from Item 13 

Suppose that a function you'd like to use when working with Investment objects is this:

 int daysHeld(const Investment *pi);        // return number of days                                            // investment has been held 

You'd like to call it like this,

 int days = daysHeld(pInv);                // error! 

but the code won't compile: daysHeld wants a raw Investment* pointer, but you're passing an object of type TR1::shared_ptr<Investment>.

You need a way to convert an object of the RAII class (in this case, tr1::shared_ptr) into the raw resource it contains (e.g., the underlying Investment*). There are two general ways to do it: explicit conversion and implicit conversion.

tr1::shared_ptr and auto_ptr both offer a get member function to perform an explicit conversion, i.e., to return (a copy of) the raw pointer inside the smart pointer object:

 int days = daysHeld(pInv.get());            // fine, passes the raw pointer                                             // in pInv to daysHeld 

Like virtually all smart pointer classes, TR1::shared_ptr and auto_ptr also overload the pointer dereferencing operators (operator-> and operator*), and this allows implicit conversion to the underlying raw pointers:

 class Investment {                         // root class for a hierarchy public:                                    // of investment types   bool isTaxFree() const;   ... }; Investment* createInvestment();                    // factory function std::tr1::shared_ptr<Investment>                   // have tr1::shared_ptr   pi1(createInvestment());                         // manage a resource bool taxable1 = !(pi1->isTaxFree());               // access resource                                                    // via operator-> ... std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr                                                    // manage a                                                    // resource bool taxable2 = !((*pi2).isTaxFree());             // access resource                                                    // via operator* ... 

Because it is sometimes necessary to get at the raw resource inside an RAII object, some RAII class designers grease the skids by offering an implicit conversion function. For example, consider this RAII class for fonts that are native to a C API:

 FontHandle getFont();               // from C API params omitted                                     // for simplicity void releaseFont(FontHandle fh);    // from the same C API class Font {                           // RAII class public:   explicit Font(FontHandle fh)         // acquire resource;   : f(fh)                              // use pass-by-value, because the   {}                                   // C API does   ~Font() { releaseFont(f); }          // release resource private:   FontHandle f;                        // the raw font resource }; 

Assuming there's a large font-related C API that deals entirely with FontHandles, there will be a frequent need to convert from Font objects to FontHandles. The Font class could offer an explicit conversion function such as get:

 class Font { public:   ...   FontHandle get() const { return f; }  // explicit conversion function   ... }; 

Unfortunately, this would require that clients call get every time they want to communicate with the API:

 void changeFontSize(FontHandle f, int newSize);     // from the C API Font f(getFont()); int newFontSize; ... changeFontSize(f.get(), newFontSize);               // explicitly convert                                                     // Font to FontHandle 

Some programmers might find the need to explicitly request such conversions off-putting enough to avoid using the class. That, in turn, would increase the chances of leaking fonts, the very thing the Font class is designed to prevent.

The alternative is to have Font offer an implicit conversion function to its FontHandle:

 class Font { public:   ...   operator FontHandle() const { return f; }        // implicit conversion function      ... }; 

That makes calling into the C API easy and natural:

 Font f(getFont()); int newFontSize; ... changeFontSize(f, newFontSize);     // implicitly convert Font                                     // to FontHandle 

The downside is that implicit conversions increase the chance of errors. For example, a client might accidently create a FontHandle when a Font was intended:

 Font f1(getFont()); ... FontHandle f2 = f1;                 // oops! meant to copy a Font                                     // object, but instead implicitly                                     // converted f1 into its underlying                                     // FontHandle, then copied that 

Now the program has a FontHandle being managed by the Font object f1, but the FontHandle is also available for direct use as f2. That's almost never good. For example, when f1 is destroyed, the font will be released, and f2 will dangle.

The decision about whether to offer explicit conversion from an RAII class to its underlying resource (e.g., via a get member function) or whether to allow implicit conversion is one that depends on the specific task the RAII class is designed to perform and the circumstances in which it is intended to be used. The best design is likely to be the one that adheres to Item 18's advice to make interfaces easy to use correctly and hard to use incorrectly. Often, an explicit conversion function like get is the preferable path, because it minimizes the chances of unintended type conversions. Sometime, however, the naturalness of use arising from implicit type conversions will tip the scales in that direction.

It may have occurred to you that functions returning the raw resource inside an RAII class are contrary to encapsulation. That's true, but it's not the design disaster it may at first appear. RAII classes don't exist to encapsulate something; they exist to ensure that a particular action resource release takes place. If desired, encapsulation of the resource can be layered on top of this primary functionality, but it's not necessary. Furthermore, some RAII classes combine true encapsulation of implementation with very loose encapsulation of the underlying resource. For example, tr1::shared_ptr encapsulates all its reference-counting machinery, but it still offers easy access to the raw pointer it contains. Like most well-designed classes, it hides what clients don't need to see, but it makes available those things that clients honestly need to access.

Things to Remember

  • APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.

  • Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.

Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
ISBN: 321334876
Year: 2006
Pages: 102 © 2008-2017.
If you may any questions please contact us: