C++学习笔记十六-模板和泛型编程(二)

C++学习笔记十六-模板和泛型编程(二)
16.4 类模板成员

1.模板作用域中模板类型的引用:
    通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字 Queue 是 Queue<Type> 缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。因此,复制构造函数定义其实等价于:
Queue<Type>(const Queue<Type> &Q): head(0), tail(0)

{ copy_elems(Q); }


    编译器不会为类中使用的其他模板的模板形参进行这样的推断,因此,在声明伙伴类 QueueItem 的指针时,必须指定类型形参:
QueueItem<Type> *head; // pointer to first element in Queue

2. 类模板成员函数 : 类模板成员函数的定义具有如下形式:
必须以关键字 template 开关,后接类的模板形参表。
必须指出它是哪个类的成员。
类名必须包含其模板形参。

从这些规则可以看到,在类外定义的 Queue 类的成员函数的形式应该是:
template <class T> ret-type Queue<T>::member-name

3.类模板成员函数的实例化:类模板的成员函数本身也是函数模板。像任何其他函数模板一样,需要使用类模板的成员函数产生该成员的实例化。类模板成员函数的模板形参由调用该函数的对象的类型确定。

调用类模板成员函数比调用类似函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换:
Queue qi; // instantiates class Queue
short s = 42;
int i = 42;
// ok: s converted to int and passed to push
qi.push(s); // instantiates Queue::push(const int&)
qi.push(i); // uses Queue::push(const int&)
f(s); // instantiates f(const short&)
f(i); // instantiates f(const int&)

4.何时实例化类和成员:类模板的成员函数只有为程序所用才进行实例化。


5.定义模板类型的对象时,该定义导致实例化类模板。定义对象也会实例化用于初始化该对象的任一构造函数,以及该构造函数调用的任意成员:
// instantiates Queue<int> class and Queue<int>::Queue()
Queue<string> qs;
qs.push("hello"); // instantiates Queue<int>::push</int></string></int></int>
Queue 类中的 QueueItem 成员是指针。类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。因此,在创建 Queue 对象进不会实例化 QueueItem 类,相反,在使用诸如 front、push 或 pop 这样的 Queue 成员时才实例化 QueueItem 类。

6.非类型形参的模板实参:这个模板有两个形参,均为非类型形参。当用户定义 Screen 对象时,必须为每个形参提供常量表达式以供使用。类在默认构造函数中使用这些形参设置默认 Screen 的尺寸。

像任意类模板一样,使用 Screen 类型时必须显式声明形参值:
Screen<24,80> hp2621; // screen 24 lines by 80 characters
非类型模板实参必须是编译时常量表达式。

7.在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:

a.普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

非模板类或非模板函数可以是类模板的友元:
template <class Type> class Bar { 
// grants access to ordinary, nontemplate class and function
friend class FooBar;
friend void fcn();
// ...
};

这个声明是说,FooBar 的成员和 fcn 函数可以访问 Bar 类的任意实例的 private 成员和 protected 成员。

b.类模板或函数模板的友元声明,授予对友元所有实例的访问权。

友元可以是类模板或函数模板:

template <class Type> class Bar {
// grants access to Foo1 or templ_fcn1 parameterized by any type
template <class T> friend class Foo1;
template <class T> friend void templ_fcn1(const T&);
// ...
};
这些友元声明使用与类本身不同的类型形参,该类型形参指的是 Foo1 和 temp1_fcn1 的类型形参。在这两种情况下,都将没有数目限制的类和函数设为 Bar 的友元。Foo1 的友元声明是说,Foo1 的友元声明是说,Foo1 的任意实例都可以访问 Bar 的任意实例的私有元素,类似地,temp_fcn1 的任意实例可以访问 Bar 的任意实例。

这个友元声明在 Bar 与其友元 Foo1 和 temp1_fcn1 的每个实例之间建立了一对多的映射。对 Bar 的每个实例而言,Foo1 或 temp1_fcn1 的所有实例都是友元。

c.只授予对类模板或函数模板的特定实例的访问权的友元声明。

template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar {
// each instantiation of Bar grants access to the
// version of Foo3 or templ_fcn3 instantiated with the same type
friend class Foo3<Type>;
friend void templ_fcn3<Type>(const Type&);
// ...
};
这些友元定义了 Bar 的特定实例与使用同一模板实参的 Foo3 或 temp1_fcn3 的实例之间的友元关系。每个 Bar 实例有一个相关的 Foo3 和 temp1_fcn3 友元:

Bar<int> bi; // Foo3<int> and templ_fcn3<int> are friends
Bar<string> bs; // Foo3<string>, templ_fcn3<string> are friends

8.声明依赖性:当授予对给定模板的所有实例的访问权时候,在作用域中不需要存在该类模板或函数模板的声明。实质上,编译器将友元声明也当作类或函数的声明对待。

想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:
template <class T> class A;
template <class T> class B {
public:
friend class A<T>; // ok: A is known to be a template
friend class C; // ok: C must be an ordinary, nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>; // error: E wasn‘t declared as a template
friend class F<int>; // error: F wasn‘t declared as a template
};

如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。

9.任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。定义成员模板模板成员声明看起来像任意模板的声明一样:

template <class Type> class Queue {
public:
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// rest of Queue class as before
private:
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
};
成员声明的开头是自己的模板形参表。构造函数和 assign 成员各有一个模板类型形参,这些函数使用该类型形参作为其函数形参的类型,它们的函数形参是指明要复制元素范围的迭代器。

10.在类外部定义成员模板:当成员模板是类模板的成员时,它的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign 函数定义的形式为

template <class T> template <class Iter> void Queue<T>::assign(Iter beg,Iter end){

destroy();

copy_elems(beg,end);

}

第一个模板形参表 template<class T> 是类模板的,第二个模板形参表 template<class Iter> 是成员模板的。

11.成员模板遵循常规访问控制:成员模板遵循与任意其他类成员一样的访问规则。如果成员模板为私有的,则只有该类的成员函数和友元可以使用该成员模板。

12.成员模板和实例化:与其他成员一样,成员模板只有在程序中使用时才实例化。

类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样。


13.类模板可以像任意其他类一样声明 static 成员。以下代码:

template <class T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};
// Each object shares the same Foo<int>::ctrand Foo<int>::count members
Foo<int> fi, fi2, fi3;
// has static members Foo<string>::ctrand Foo<string>::count
Foo<string> fs;
每个实例化表示截然不同的类型,所以给定实例外星人所有对象都共享一个 static 成员。因此,Foo<int> 类型的任意对象共享同一 static 成员 ctr,Foo<string> 类型的对象共享另一个不同的 ctr 成员。

14.使用类模板的 static 成员:

通常,可以通过类类型的对象访问类模板的 static 成员,或者通过使用作用域操作符直接访问成员。当然,当试图通过类使用 static 成员的时候,必须引用实际的实例化:

Foo<int> fi, fi2; // instantiates Foo<int> class
size_t ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // ok: uses Foo<int>::count
ct = fi2.count(); // ok: uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
与任意其他成员函数一样,static 成员函数只有在程序中使用时才进行实例化。

15.定义 static 成员:像使用任意其他 static 数据成员一样,必须在类外部出现数据成员的定义。在类模板含有 static 成员的情况下,成员定义必须指出它是类模板的成员:

template <class T>
size_t Foo<T>::ctr = 0; // define and initialize ctr
static 数据成员像定义在类外部的任意其他类成员一样定义,它用关键字 template 开头,后面接着类模板形参表和类名。在这个例子中,static 数据成员的名字以 Foo<T>:: 为前缀,表示成员属于类模板 Foo。

16.6 模板特化

1.函数模板的特化:模板特化(template specialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:
a.关键字 template 后面接一对空的尖括号(<>);
b.再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;
c.函数形参表;
d.函数体。

当模板形参类型绑定到 const char* 时,compare 函数的特化:

// special version of compare to handle C-style character strings
template <>
int compare<const char*>(const char* const &v1,
const char* const &v2)
{
return strcmp(v1, v2);
}
现在,当调用 compare 函数的时候,传给它两个字符指针,编译器将调用特化版本。编译器将为任意其他实参类型(包括普通 char*)调用泛型版本:

2.声明特化:与任意函数一样,函数模板特化可以声明而无须定义。模板特化声明看起来与定义很像,但省略了函数体:

// declaration of function template explicit specialization
template<>
int compare<const char*>(const char* const&,
const char* const&);

这个声明由一个后接返回类型的空模板形参表(template<>),后接一对尖括号中指定的显式模板实参的函数名(可选),以及函数形参表构成。模板特化必须总是包含空模板形参说明符,即 template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显式指定模板实参:

// error: invalid specialization declarations
// missing template<>
int compare<const char*>(const char* const&,
const char* const&);

// error: function parameter list missing
template<> int compare<const char*>;

// ok: explicit template argument const char* deduced from parameter types
template<> int compare(const char* const&,
const char* const&);

3.函数重载与模板特化:当定义非模板函数的时候,对实参应用常规转换;当特化模板的时候,对实参类型不应用转换。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例。

4.不是总能检测到重复定义:如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件中出现。不能在一些文件中从泛型模板定义实例化一个函数模板,而在其他文件中为同一模板实参集合特化该函数模板。

与其他函数声明一样,应在一个头文件中包含模板特化的声明,然后使用该特化的每个源文件包含该头文件。

5.为 C 风格字符串的 Queue 提供正确行为的一种途径,是为 const char* 定义整个类的特化版本:

/* definition of specialization for const char*
* this class forwards its work to Queue<string>;
* the push function translates the const char* parameter to a string
* the front functions return a string rather than a const char*
*/
template<> class Queue<const char*> {
public:
// no copy control: Synthesized versions work for this class
// similarly, no need for explicit default constructor either
void push(const char*);
void pop() {real_queue.pop();}
bool empty() const {return real_queue.empty();}
// Note: return type does not match template parameter type
std::string front() {return real_queue.front();}
const std::string &front() const
{return real_queue.front();}
private:
Queue<std::string> real_queue; // forward calls to real_queue
};

6.类特化定义:在类特化外部定义成员时,成员之前不能加 template<> 标记。

我们的类只在类的外部定义了一个成员:
void Queue<const char*>::push(const char* val)
{
return real_queue.push(val);

}


7.特化成员而不特化类:成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参表开头:
// push and pop specialized for const char*
template <>
void Queue<const char*>::push(const char* const &);

template <> void Queue<const char*>::pop();


8.类模板的部分特化:
如果类模板有一个以上的模板形参,我们也许想要特化某些模板形参而非全部。使用类模板的部分特化可以做到这一点:

template <class T1, class T2>
class some_template {
// ...
};
// partial specialization: fixes T2 as int and allows T1 to vary
template <class T1>
class some_template<T1, int> {
// ...
};
类模板的部分特化本身也是模板。部分特化的定义看来像模板定义,这种定义以关键字 template 开头,接着是由尖括号(<>)括住的模板形参表。部分特化的模板形参表是对应的类模板定义形参表的子集。some_template 的部分特化只有一个名为 T1 的模板类型形参,第二个模板形参 T2 的实参已知为 int。部分特化的模板形参表只列出未知模板实参的那些形参。

9.使用类模板的部分特化:

部分特化与对应类模板有相同名字,即这里的 some_template。类模板的名字后面必须接着模板实参列表,前面例子中,模板实参列表是 <T1,int>。因为第一个模板形参的实参值未知,实参列表使用模板形参名 T1 作为占位符,另一个实参是类型 int,为 int 而部分特化模板。

像任何其他类模板一样,部分特化是在程序中使用时隐式实例化:

some_template<int, string> foo; // uses template
some_template<string, int> bar; // uses partial specialization
注意第二个变量的类型,形参为 string 和 int 的 some_template,既可以从普通类模板定义实例化,也可以从部分特化实例化。为什么选择部分特化来实例化该模板呢?当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。foo 的实例化类型与提供的部分特化不匹配,因此,foo 的类型必然从通用类模板实例化,将 int 绑定到 T1 并将 string 绑定到 T2。部分特化只用于实例化第二个类型为 int 的 some_template 类型。

部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。

16.7 重载与函数模板

函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。

当然,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性。

1.如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下:

a. 为这个函数名建立候选函数集合,包括:
与被调用函数名字相同的任意普通函数。
任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参。
b. 确定哪些普通函数是可行的(如果有可行函数的话)。候选集合中的每个模板实例都 可行的,因为模板实参推断保证函数可以被调用。
c. 如果需要转换来进行调用,根据转换的种类排列可靠函数,记住,调用模板函数实例所允许的转换是有限的。
如果只有一个函数可选,就调用这个函数。
如果调用有二义性,从可行函数集合中去掉所有函数模板实例。
d. 重新排列去掉函数模板实例的可行函数。
如果只有一个函数可选,就调用这个函数。
否则,调用有二义性。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。