Smart Pointers

[Previous] [Next]

A smart pointer is a C++ class for wrapping "dumb" pointers. By wrapping a pointer in a class (and specifically in a template), you can make sure certain mundane, boilerplate-type operations are taken care of automatically instead of deferred to the client. One good example of such an operation involves making sure pointers are initialized correctly so that embarrassing calls through randomly assigned pointers don't happen. Another is making sure certain initialization tasks are accomplished before function calls are made through a pointer.

Inventing a Smart Pointer

Let's invent a smart pointer for the musician model described above. First we'll consider several types of musicians based on the CMusician class, as shown here:

 class CGuitarist : public CMusician { public:     CGuitarist() : CMusician("Guitar") {}     CGuitarist(char* szInstrument) :         CMusician(szInstrument) {     }     void PlayLead() {         MessageBox(NULL, "Play in a boring style",                    "Joe Guitarist", MB_OK);     } }; class CDrummer : public CMusician { public:     CDrummer() : CMusician("Drums") {}     CDrummer(char* szInstrument) :         CMusician(szInstrument) {     }     void BangAway() {         MessageBox(NULL, "Play in a boring style",                    "Joe Drummer", MB_OK);     } }; class CVocalist : public CMusician { public:     CVocalist() : CMusician("Vocals") {}     CVocalist(char* szInstrument) :         CMusician(szInstrument) {     }     void Sing() {         MessageBox(NULL, "Sing in a boring style",                    "Joe Singer", MB_OK);     } }; class CBassist : public CMusician  { public:     CBassist(): CMusician("Bass") {}     CBassist(char* szInstrument) :         CMusician(szInstrument) {     }     void ThumpAway() {         MessageBox(NULL, "Play in a boring style",                    "Joe Bassist", MB_OK);     } }; 

Once you grasp the different musician types, consider a template-based class named SmartMusician that can wrap a musician object, as shown here:

 template <class T> class CSmartMusician {     T* m_pT; public:     CSmartMusician(T& rT) {         m_pT = &rT;         HelpDrummerSetup();     }     ~CSmartMusician() {         HelpDrummerCleanup();     }     void HelpDrummerCleanup() {         MessageBox(NULL, "Helping drummer clean up",                     NULL, MB_OK);     }     void HelpDrummerSetup() {         MessageBox(NULL, "Carry them drums",                     NULL, MB_OK);     }     CSmartMusician& operator=(const CSmartMusician& rMusician) {         return *this;     }     T* operator->() const {         MessageBox(NULL,                    "Dereferencing pointer. Everyone happy?",                    NULL, MB_OK);         return m_pT;     } }; 

This CSmartMusician template class wraps a pointer of type CMusician. Because the CSmartMusician class is based on a template, the class can provide generic functionality regardless of the type of data associated with the class.

We want the smart pointer to handle all musicians, including guitarists, bass players, vocalists, and drummers. We accomplish this flexibility by placing the template <class T> statement right before declaring the class. The CSmartMusician template class includes a pointer to the type of musician for which the class will be defined. (The pointer is a data member named m_pT.) The CSmartMusician's constructor takes a pointer to that type as a parameter and assigns the parameter to m_pT.

In addition to wrapping a pointer, the CSmartMusician class implements several operators. The most important one is the -> operator (the indirection operator). This operator is the workhorse of any smart pointer class, and overloading the pointer operator is what turns a regular class into a smart pointer class. Normally, using the pointer-based member-selection operator on a regular C++ dumb pointer tells the compiler to select a member belonging to the class or structure being pointed to. By implementing the pointer-based member-selection operator, you provide a way to hook into memory dereference operations and have some boilerplate code called every time the client calls a method. In the musicians example, the smart constructor makes sure the drummer's equipment is set up before the band begins playing. This example is contrived, though it does illustrate the point: in real life, you might want to put in some other kind of hook.

Adding the -> operator to the class causes the class to behave like the pointers built into C++. To act like native C++ pointers in other ways, smart pointer classes need to implement the other standard operators, such as the dereferencing and assignment operators.

Using Smart Pointers

Using smart pointers is similar to using the regular built-in C++ pointers. To illustrate this similarity, let's check out this example of a client using the plain-vanilla musician classes:

 void UseDumbMusicians() {     MessageBox(NULL, "Not using smart pointers yet",                NULL, MB_OK);     CGuitarist guitarist("Guitar");     CDrummer drummer("Drums");     CBassist bass("Bass");     guitarist.Play();     drummer.Play();     bass.Play(); } 

No surprises here—executing this code causes the musicians simply to come in and play. Running this function causes three message boxes to be displayed, each indicating that a different musician is playing. The poor drummer doesn't get any help setting up his stuff. However, we want to use the smart musicians—the ones who help the drummer set up before playing. The following code wraps the musician objects in the smart pointer class:

 void UseSmartMusician() {     MessageBox(NULL, "Using smart pointers now...",                NULL, MB_OK);     CGuitarist guitarist("Guitar");     CDrummer drummer("Drums");     CBassist bass("Bass");     CSmartMusician<CMusician> smartguitarist(guitarist);      CSmartMusician<CMusician> smartdrummer(drummer);      CSmartMusician<CMusician> smartbass(bass);      smartguitarist->Play();     smartdrummer->Play();     smartbass->Play(); } 

Instead of letting the musicians behave in their standard unhelpful way (as the previous example did), this example wraps the guitarist, bassist, and drummer objects using the smart pointer template. When the client asks the smart musicians to do the work, the smart musicians will automatically help the drummer set up before they begin to play.

This example illustrates one use for C++ templates—wrapping C++ classes so that certain generic setup and tear-down code executes. C++ also lets you override the -> operator as a hook to make sure everything is kosher before making function calls.

As it turns out, smart pointers aren't the only use for templates. You can also use templates to provide client code with an easy way to specialize the behavior of a class by passing in algorithms as template parameters. Let's see how.

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: