More C++ Idioms/Smart Pointer
Smart Pointer
[edit | edit source]Intent
[edit | edit source]To relieve the burden of duplicating changes to the signature of the body class in its handle class when Handle Body idiom or Envelope Letter idiom is in use.
Also Known As
[edit | edit source]- En Masse (whole) Delegation
Motivation
[edit | edit source]When Handle/body idiom is used, it may become necessary to duplicate the interface of the body class in the handle class because handles are used by the user code. This duplication is often tedious and error prone. Smart Pointer idiom is used to relieve this burden. Smart Pointer idiom is often used along with some sort of "smartness" in the handle class such as reference counting, automatic ownership management and so on.
Solution and Sample Code
[edit | edit source]There are at least two overlapping ways of implementing the smart pointer idiom depending upon the intended use.
- Completely pointer like semantics
- Less pointer like semantics
Both of the above variations define an overloaded arrow operator in the so called "handle" class. Lets begin with the completely pointer like semantics.
class Body;
class Handle // Completely pointer like semantics
{
public:
void set (Body *b) { body_ = b; }
Body * operator -> () const throw()
{
return body_;
}
Body & operator * () const throw ()
{
return *body_;
}
private:
mutable Body *body_;
};
int main (void)
{
Handle h;
h.set(new Body());
h->foo(); // A way of invoking Body::foo()
(*h).foo(); // Another way of invoking Body::foo()
}
Using the -> operator alone mitigates the problem of duplicating interface of body class in the handle class. An alternative is to overload deference (*) operator as show in the code snippet above but it is not as natural as the earlier one. If the Handle abstraction is a some sort of pointer abstraction then both the overloaded operators should be provided (e.g., std::auto_ptr, boost::shared_ptr). If the Handle abstraction is not a pointer like abstraction then * operator need not be provided. Instead, it could useful to provide const overloaded set of arrow operators because client always interacts with the handle class objects. For the client code, handle is the object and hence const-ness of the handle should be propagated to corresponding body whenever it's appropriate. In general, the obscure behavior of being able to modify a non-const body object from within a constant handle should be avoided. Unlike pure pointer semantics, in some cases, automatic type conversion from Handle class to Body class is also desirable.
class Body;
class Handle // Less pointer like semantics
{
public:
void set (Body *b) { body_ = b; }
Body * operator -> () throw()
{
return body_;
}
Body const * operator -> () const throw()
{
return body_;
}
operator const Body & () const // type conversion
{
return *body_;
}
operator Body & () // type conversion
{
return *body_;
}
// No operator *()
private:
mutable Body *body_;
};
int main (void)
{
Handle const h;
h.set(new Body());
h->foo(); // compiles only if Body::foo() is a const function.
}
An alternative to using member conversion functions is to use the Non-member get idiom as shown below. The overloaded non-member get() functions must be in the same namespace as the Handle class according to the Interface Principle.
namespace H {
class Body;
class Handle { ... }; // As per above.
Body const & get (Handle const &h)
{
return *h.body_;
}
Body & get (Handle &h)
{
return *h.body_;
}
} // end namespace H.
int main (void)
{
H::Handle const h;
h.set(new Body());
get(h).foo(); // compiles only if Body::foo() is a const function.
}
Known Uses
[edit | edit source]- std::auto_ptr (Completely pointer like semantics)
- boost::shared_ptr (Completely pointer like semantics)
- CORBA Var types in C++ (TAO_Seq_Var_Base_T< T > Class in TAO (The ACE ORB) - less pointer like semantics)