这章后面部分C++ Primer Plus有一笔带过的意思,讲得不太详细。可能是这部分不重要吧。不过这可苦了我,很多结论都是上网查资料和自己验证才能得出,时间当然费不少……
2012-3-24(Reusing Code in C++ part V)
15. Template Versatility
We can apply the same technique to template classes as we do to regular classes. Template classes can serve as base classes, and they can be component classes. They can themselves be type argument to other templates. For example
template <typename T>
class ExDArray: public DArray<T> //inheritance
{/*class declaration*/};
template <typename T>
class Stack //use template class as component
{
private:
DArray<T> items;
int top;
// other section
};
NArray<DArray<int>,10> mix; //nested templates
I. Using More Than One Type Parameter
You can have a template with more than one type parameter. For example:
template <typename T1,typename T2>
class Exm
{
private:
T1 a;
T2 b;
//other portion
};
The rest of class declaration is similar to template class with single parameter.
II. Default Type Template Parameter
We can provide default values for type parameter:
template <typename T1,typenameT2 = int>
class Exm{…};
Then the declaration:
Exm<double> e1
is equivalent to
Exm<double,int> e2
If your template class has only one parameter,like:
template <typename T = int>
classDArray {…};
can also use this feature:
DArray<> d; //equals toDArray<int> d
Note that we can provide default values for class template type parameters, while function template parameters can't do so.However, we can provide default values for non-type parameters for both class and function templates.
16. Template Specialization
Class templates are like function templates in that we can have implicit instantiations, explicit instantiations and explicit specializations, collectively known as specializations. Learning this part we can review the specialization of template functions.
I. Implicit Instantiation.
If we declare one or more objects indicating the desired type, the compiler will generate a specialized class definition, using the recipe provided by the general template. This procedure is called implicitin stantiation. The compiler doesn't generate an implicit instantiation of the class until it needs an object:
//a DArray<int> type pointer, no object needed yet
DArray<int>* d;
d = new DArray<int>(5); //an object is needed
II. Explicit Instantiation
The compiler generates an explicit instantiationof class declaration when you declare a class by using keyword template and indicating the desired type or types. The declaration should be in the same namespace as the template definition. For example, if we define a template class in a header file (in thestd namespace), we can't declare explicit instantiations in other namespace, such as in the main function.
The following statement
template class NArray<int,20>; //generate NArray<int,20> class
the compiler will generates the class definition,including method definition, even though no object of the class has yet been created or mentioned.
III. Explicit Specializations
Explicit specializations can be useful if you need to modify a template to behave differently when instantiated for a particular type. For example, if we want the DArray<double> class has average() const function to compute the average of all elements:
template <> class DArray<double>
{
public:
double average() const;
//omitted portion
};
We can regarded this class (DArray<double>) as an overloading version of DArray<double>that generated by template. So that the omitted portion can be totally different from or the same as general one.
After declaring the explicit specialization,requesting for a DArray template of double will use this specialized definition instead of the more general template definition:
DArray<int> d1; //use general definition
DArray<double> d2; //use specialized definition
IV. Partial Specializations
C++ allows for partial specializations:
template <typenameT1> //specialization with T2 set to int
class Exm<T1,int> {…};
Particularly, specifying all types leads to anempty bracket pair and a complete explicit specialization:
template <> class Exm<int,int>{…};
The compiler uses the most specialized template:
Exm<double, double> e2; //use the general Exm template
Exm<double, int> e2; //use Exm<T1, int>
Exm<int, int> e3; //use Exm<int, int>
17. Member Template
A template can be a member of structure, class, or template class. So we can use templates as follows (copy from C++ Primer Plus 5th Edition ):
// tempmemb.cpp -- template members
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class beta
{
private:
template <typename V> // nested template class member
class hold
{
private:
V val;
public:
hold(V v = 0) : val(v) {}
void show() const { cout << val << endl; }
V Value() const { return val; }
};
hold<T> q; // template object
hold<int> n; // template object
public:
beta( T t, int i) : q(t), n(i) {}
template<typename U> // template method
U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }
void Show() const { q.show(); n.show();}
};
int main()
{
beta<double> guy(3.5, 3);
guy.Show();
cout << guy.blab(10, 2.3) << endl;
cout << "Done\n";
return 0;
}
We need to pay attention to the member template function
blab()
. This program's output is
3.5
3
28
Done
This indicates that U is set to type int. If we replace 10 with 10.0, the output will change:
3.5
3
28.2609
Done
Because U is set to type double.
Unlike the first parameter, the type of the second parameter is not set by the function call. That is to say,guy.blab(10,3) would still implement blab() as blab(int, double).
17. Template Classes and Friends
Before we discuss about this complex portion, we have better recall the key point of friend function:
- Friends are only ordinary functions but can access private members.
- Friends do not belong to class(not its member function).
Next to business. We can classify friends of templates into 3 categories:
- Non-template friends
- Bound template friends, meaning the type of the friends is determined by the type of the class when a class is instantiated.
- Unbound template friends, meaning that all specializations of the friends are friends to each specialization of the class.
I. Non-Template Friends
Just as its name implies, friends themselves are not template functions. But their can either have template class object parameters or not. So non-template friends have two kinds of form. For instance:
template <typename T>
class Exm1
{
friend void f1(); //Form I:no object parameter
//Form II: has template class parameter
friend void f2(Exm1<T> e);
//… …omitted portion
};
Two kinds of non-template friends are friend to all classes generated by template class. However, in this example, we have only one
f1()
, so
f1()
can only access limited private members of the classes generated by template when typing its function body.
Note that f2()is not itself a template function; it just has a parameter that is template. That is means that we have to define explicit specializations for the friends we plan to use. This is a kink of overloading:
void f2(Exm1<int> e) {…} //friend for class Exm1<int>
void f2(Exm1<double> e){…} //friend for class Exm1<double>
Here is an example:
#include <iostream>
using namespace std;
template <typename T>
class Exm1
{
private:
T item;
static int obCounter;
public:
Exm1(const T& t): item(t) {obCounter++; }
~Exm1(){obCounter--; }
friend void counts();
friend ostream& operator <<(ostream& os,const Exm1<T>& e);
};
template <typename T> int Exm1<T>::obCounter = 0;
void counts()
{
cout << "Number of Exm1<int> objects:"
<< Exm1<int>::obCounter << endl;
}
ostream& operator << (ostream& os,const Exm1<int>& e)
{
os << e.item;
return os;
}
int main()
{
Exm1<int> ie1(10);
Exm1<int> ie2(20);
Exm1<double> de(5.5);
counts();
cout << "Value of private member: "
<< ie1 << " " << ie2 << endl;
//cout << de << endl;
}
output:
Number of Exm1<int>objects:2
Value of private member: 10 20
counts()can access all obCounter of classes generated by template, but in this example it use Exm1<int>::obCounter. The comment line is invalid, because we have not defined the friend of Exm1<double> yet.
II. Bound Template Friends
As we can see, non-template friends can not befriend to all classes generated by template(so operator <<()can't apply to all class objects). So we need to use a new technique to generate friends for each instantiation. That is bound template friends. The key of this technique is set class templates' type to the type of friends
Here is an example:
#include <iostream>
using namespace std;
template <typename FT> int counts();
template <typename CT>
class Exm2
{
private:
CT item;
static int obCounter;
public:
Exm2(const CT& t): item(t) {obCounter++; }
~Exm2(){obCounter--; }
friend int counts<CT>(); //explicit instantiation
friend ostream& operator <<(ostream& os,const Exm2<CT>& e)
{return (os << e.item); }
};
template <typename T> int Exm2<T>::obCounter = 0;
template <typename FT>
int counts()
{
return Exm2<FT>::obCounter;
}
int main()
{
Exm2<int> ie1(10);
Exm2<int> ie2(20);
Exm2<double> de(5.5);
cout << "Objects of Exm2<int>: " << counts<int>() << endl;
cout << "Objects of Exm2<double>: " << counts<double>() << endl;
cout << "Value of private member: \n";
cout << "Exm2<int>: " << ie1 << " " << ie2 << endl;
cout << "Exm2<double>: " << de << endl;
}
output:
Objects of Exm2<int>: 2
Objects of Exm2<double>: 1
Value of private member:
Exm2<int>:10 20
Exm2<double>:5.5
This program seems a little strange, but it runs well. We have two main methods to make this technique possible:
- Declare and define a template function, friend is the explicit instantiation of it.
- Declare a template friend function, define it inside or outside the template class.
First of all, we can wipe out the impossible one:declare a template friend function and define it outside the template class. Because what your declare and define are different template functions. For example:
What your declare is:
friend ostream& operator<<(ostream& os,constExm2<CT>& e)
and what your define is
template <typename FT>
ostream& operator << (ostream& os, const Exm2<FT>& e)
{/*function body*/}
The form of latter is actually a template definition, which is independent of the former. When compiler generates declaration, no one set class type(
CT
) to function(
FT
) type.
i.counts()
This friend function has no symbol to distinguish,for example, between countd()belong to Exm2<int> and counts()belong to Exm2<double>. So, the first method is the only approach.
ii. operator <<()
We choose the second way in this example, the first one is suitable for operator<()? The answer is no. The reason is ambiguous:
template <typename FT> //oridinary template function
ostream& operator <<(ostream& os, const FT& e)
{/*function body*/}
//explicit instantiation set Exm2<CT> to FT
friend ostream& operator << <>(ostream& os,const Exm2<CT>& e);
When the program attempt to print a string:
cout <<"string"; //invokeoperator <<(cout,"string")
operator <<(cout,"string") can match operatoe <<(ostream&,string&)(set FT to string) and operator<< in standard library.
So, declare and define friend inside the class is the only choice.
III. Unbound Template Friend
By declaring a template inside a class ,we can create unbound friend functions for which every function specialization is a friend to every class specialization:
(copy from C++ Primer Plus 5th Edition)
// manyfrnd.cpp -- unbound template friend to a template class
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class ManyFriend
{
private:
T item;
public:
ManyFriend(const T & i) : item(i) {}
template <typename C, typename D> friend void show2(C &, D &);
};
template <typename C, typename D> void show2(C & c, D & d)
{
cout << c.item << ", " << d.item << endl;
}
int main()
{
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(10.5);
cout << "hfi1, hfi2: ";
show2(hfi1, hfi2);
cout << "hfdb, hfi2: ";
show2(hfdb, hfi2);
return 0;
}
Output
hfi1, hfi2: 10,2
hfdb,hfi2: 10.5, 20