[ Team LiB ] |
![]() ![]() |
Gotcha #88: Misunderstanding Templated Copy OperationsA common use of template member functions is to implement constructors. For example, many standard containers have a template constructor that allows initialization of the container by a sequence: template <typename T> class Cont { public: template <typename In> Cont( In b, In e ); // . . . }; Use of the template constructor allows the container to be initialized by an input sequence derived from any source and renders the container much more useful than it would be otherwise. The standard auto_ptr template uses template member functions as well: template <class X> class auto_ptr { public: auto_ptr( auto_ptr & ); // copy ctor template <class Y> auto_ptr( auto_ptr<Y> & ); auto_ptr &operator =( auto_ptr & ); // copy assignment template <class Y> auto_ptr &operator =( auto_ptr<Y> & ); // . . . }; Notice, however, that auto_ptr, in addition to its template constructor and assignment operator, has explicitly declared its copy operations. This is necessary for correct behavior, since template member functions are never instantiated for use as copy operations. As always, in the absence of an explicitly declared copy constructor or copy assignment operator, the compiler will still generate one as necessary. This often-overlooked exception to template instantiation is an occasional source of problems:
enum Currency { CAD, DM, USD, Yen }; template <Currency currency> class Money { public: Money( double amt ); template <Currency otherCurrency> Money( const Money<otherCurrency> & ); template <Currency otherCurrency> Money &operator =( const Money<otherCurrency> & ); ~Money(); double get_amount() const { return amt_; } // . . . private: Curve *myCurve_; double amt_; }; // . . . Money<Yen> acct1( 1000000.00 ); Money<DM> acct2( 123.45 ); Money<Yen> acct3( acct2 ); // template ctor Money<Yen> acct4( acct1 ); // compiler-generated copy ctor! acct3 = acct2; // template assignment acct4 = acct1; // compiler-generated assignment! This is simply a new version of a very old problem in C++ class design. Whenever a class contains a pointer or other resource handle that doesn't manage itself, it's important to control the class's copy operations carefully, so as to avoid leaking or aliasing the resource. Consider a fragment of the implementation of the template assignment above:
template <Currency currency> template <Currency otherCurrency> Money<currency> & Money<currency>::operator =( const Money<otherCurrency> &rhs ) { amt_ = myCurve_-> convert( currency, otherCurrency, rhs.get_amount() ); } Clearly, it's important to the implementation of Money that the Curve referred to by myCurve_ not be modified or shared during assignment. However, this is exactly what the compiler-generated copy operations will do, and will do silently:
template <Currency currency>
Money<currency> &
Money<currency>::operator =( const Money<currency> &that ) {
myCurve_ = that.myCurve_; // leak, alias, and change of curve!
amt_ = myCurve_->
convert( currency, otherCurrency, rhs.get_amount() );
}
The Money template should implement its copy operations explicitly. Copy operations are never implemented by template member functions. Always consider copy operations in the design of any class (see Gotcha #49). |
[ Team LiB ] |
![]() ![]() |