6.3 Handling Template Type Parameters
Handling a template type parameter is somewhat more complicated than handling an explicit parameter type. For example, to declare an explicit int parameter to a function, we write
bool find( int val );
passing it by value. However, to declare an explicit Matrix class parameter to a function, we instead write
bool find( const Matrix &val );
passing it by reference in order not to generate an unnecessary copy of the Matrix class object. Our program is not wrong if we declare find() as follows:
// not wrong, but inefficient
bool find( Matrix val );
It simply takes longer to reach the same conclusion and is likely to be criticized by readers of our code, particularly if find() is called frequently within the program.
When we manipulate a template type parameter, we don't really know whether the actual type supplied by the user will be a built-in type:
BinaryTree<int> bti;
In that case the pass by value parameter list of find() is preferred. If it is a class type
BinaryTree<Matrix> btm;
the pass by reference parameter list of find() is preferred.
In practice, both built-in and class types are likely to be specified as actual types to the class template. The recommended programming strategy is to treat the template type parameter as if it is a class type. As a function parameter, for example, this means that we declare it as a const reference rather than pass it by value.
Within the definition of a constructor, we initialize each type parameter within the member initialization list
// preferred initialization method for
// type parameter passed to a constructor
template <typename valType>
inline BTnode<valType>::
BTnode( const valType &val )
// just in case valType is a class type
: _val( val )
{
_cnt = 1;
_lchild = _rchild = 0;
}
rather than within the body of the constructor:
template <typename valType>
inline BTnode<valType>::
BTnode( const valType &val )
{
// not recommended; could be a class type
_val = val;
// ok: these types are invariant ...
_cnt = 1;
_lchild = _rchild = 0;
}
This guarantees optimal performance if the user specifies a class type as the actual type of valType. For example, if I write
BTnode<int> btni( 42 );
there is no difference in performance between the two forms. However, if I write
BTnode<Matrix> btnm( transform_matrix );
there is a difference in performance. The assignment of _val in the constructor body requires two steps: (1) The default Matrix constructor is applied to _val before execution of the constructor body, and (2) the copy assignment operator of _val with val is applied within the body. The initialization of _val within the constructor's member initialization list requires only a single step: the copy construction of _val with val.
Again, it is not that our program is wrong either in passing valType by value or in assigning the valType data member within the constructor body. But they do take more time and are considered a sign of an inexperienced C++ programmer.
At this point in learning C++ you shouldn't be overly concerned with efficiency. However, it is useful to point out these two cases because they are common beginner mistakes, and a simple heads-up is usually enough to correct them. Enough said!
|