淘宝客网站开发平台,网站页面设计工具,广告公司网站设计策划书,建设银行网银盾连接不上网站类模板(参考《C Templates 英文版第二版》)
Chapter 1 类模板
与函数相似,类也可以被一个或者多个类型参数化
在这章,我们使用栈作为例子
2.1 类模板stack的实现
#include vector
#include casserttemplatetypename T
class Stack
{
private:std…类模板(参考《C Templates 英文版第二版》)
Chapter 1 类模板
与函数相似,类也可以被一个或者多个类型参数化
在这章,我们使用栈作为例子
2.1 类模板stack的实现
#include vector
#include casserttemplatetypename T
class Stack
{
private:std::vectorT elems;public:void push(T const elem);void pop();T const top() const;bool empty() const{return elems.empty();}
};template typename T
void StackT::push(T const elem)
{elems.push_back(elem);
}template typename T
void StackT::pop()
{assert(!elems.empty());elems.pop_back();
}template typename T
T const StackT::top() const
{assert(!elems.empty());return elems.back();
}这个类模板通过一个STL里面的类模板vector实现.这样,我们就不用去实现内存管理,拷贝构造,赋值运算符等内容,专注于类的实现
2.1.2 成员函数的实现
如果你要定义一个类模板的成员函数,你必须去指定他是一个模板,并且满足类模板的全部类型,就像下面这样:
void StackT::push(T const elem)
{elems.push_back(elem);
}在这种情况下,push_back()被调用,向vector添加一个elem
注意:pop_back()函数只移除最后一个元素,但是不返回它,只是因为这种行为(只移除)是异常安全的,是不可能实现一个移除并返回最后一个元素的异常安全函数的1
2.2 stack实用类模板
#include max1.hpp
#include iostream
#include string
#include format
#include type_traits
#include stack1.hpp
#include iostream
#include stringint main()
{Stackint intStack; // stack of intsStackstd::string stringStack; // stack of strings// manipulate int stackintStack.push(7);std::cout intStack.top() \n;// manipulate string stackstringStack.push(hello);std::cout stringStack.top() \n;stringStack.pop();
}C17可以这么写2
#include vector
int main()
{std::vector intVector{ 1,2 }; //省略
}注意:类模板只会实例化被调用的函数,再上个例子中,int string的top(),push()都被实例化,但是pop()只在传入string实例化一次
2.3 类模板的部分使用(Partial Usage of Class Templates)
一个类模板通常对模板参数提供多种操作,这可能会给你错觉:类模板必须提供模板参数所有成员函数的操作.
但事实上不是这样,类模板只会提供被模板参数用到的成员函数
在上文中加入以下代码:
void printOn(std::ostream strm){for (T const elem : elems){strm elem std::endl;}}运行:
Stackstd::pairint, int ps;ps.push({ 2,5 });ps.push({ 3,5 });std::cout ps.top().first std::endl; //正确//ps.printOn(std::cout); // 错误只有当你调用printOn()时,才会报错,说明类模板只会实例化需要的成员函数,
2.3.1 概念(Concepts)
类模板、函数模板及非模板函数常为类模板成员可以与制约关联制约指定模板实参上的要求这能用于选择最准确的函数重载和模板特化。
制约亦可用于限制变量声明和函数返回类型中的自动类型推导为只有满足指定要求的类型。
这种要求的具名集合被称为概念。每个概念都是谓词于编译时求值并成为模板接口的一部分它在其中用作制约
#include locale
#include string
using namespace std::literals;// 概念 EqualityComparable 的声明任何有该类型值 a 和 b
// 而表达式 ab 可编译而其结果可转换为 bool 的 T 类型满足它
template typename T
concept bool EqualityComparable requires(T a, T b)
{{a b} - bool;
};void f(EqualityComparable); // 有制约函数模板的声明
// templatetypename T
// void f(T) requires EqualityComparableT; // 相同的长形式int main()
{f(abcs); // OK std::string 为 EqualityComparablef(std::use_facetstd::ctypechar(std::locale{})); // 错误非 EqualityComparable
}更多请参见:制约与概念 - cppreference.com
2.4 友元
与其使用printOn函数打印元素不如重载operator然而通常operator会实现为非成员函数。下面在类内定义友元它是一个普通函数
templatetypename T
class Stack {...void printOn(std::ostream os) const;friend std::ostream operator(std::ostream os, const StackT stack){stack.printOn(os); return os;}
};如果在类外定义友元类模板参数不可见事情会复杂很多
templatetypename T
class Stack {...friend std::ostream operator(std::ostream, const StackT);
};std::ostream operator(std::ostream os,const StackT stack) // 错误类模板参数T不可见
{stack.printOn(os);return os;
}有两个解决方案: 一是隐式声明一个新的函数模板并使用不同的模板参数 templatetypename T
class Stack {… templatetypename U friend std::ostream operator(std::ostream, const StackU);
};// 类外定义
templatetypename U
std::ostream operator(std::ostream os, const StackU stack)
{stack.printOn(os);return os;
}二是将友元前置声明为模板而友元参数中包含类模板这样就必须先前置声明类模板 templatetypename T // operator中参数中要求Stack模板可见
class Stack;templatetypename T
std::ostream operator(std::ostream, const StackT);// 随后就可以将其声明为友元
templatetypename T
class Stack {…friend std::ostream operator T (std::ostream, const StackT);
};// 类外定义
templatetypename T
std::ostream operator(std::ostream os, const StackT stack)
{stack.printOn(os);return os;
}
同样函数只有被调用到时才实例化元素没有定义operator时也可以使用这个类只有调用operator时才会出错
Stackstd::pairint, int s; // std::pair没有定义operator
s.push({1, 2}); // OK
s.push({3, 4}); // OK
std::cout s.top().first s.top().second; // 34
std::cout s \n; // 错误元素类型不支持operator2.5 类模板特化
模板的实际应用中有一些概念和应用很容易让人混淆现在就分析一下模板的特化和实例化。编写模板的代码最终的目的是应用而在实际应用的过程中大家最经常使用的是模板的实例化。也就是说模板说一族类或函数的抽象那么要使用它就需要把它应用到某个具体的类或者函数上。 另外还有一个绕不开的就是特化specialization,从目前的教材来看有两种理解
凡是把模板用具体的值来替代的过程都叫特化。如果这么理解实例化也是特化的一种。特化是普通模板通过具体的值来替换后不能满足一些特定的情况下的要求需要对其进行特别的处理包括偏特化和全特化。
全特化写法
template
class Stackstd::string
{...
};
实例:
#include stack1.hpp
#include deque
#include string
#include casserttemplate
class Stackstd::string {private:std::dequestd::string elems; // elementspublic:void push(std::string const); // push elementvoid pop(); // pop elementstd::string const top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();}
};void Stackstd::string::push (std::string const elem)
{elems.push_back(elem); // append copy of passed elem
}void Stackstd::string::pop ()
{assert(!elems.empty());elems.pop_back(); // remove last element
}std::string const Stackstd::string::top () const
{assert(!elems.empty());return elems.back(); // return copy of last element
}
当我们使用std::string作为模板参数时,就会实例化这个用std::string特化的类.
我的理解:模板的特化就是为了处理一些非一般情况
2.6 偏特化
函数是没有偏特化的。所以这里只是介绍类的偏特化。所谓偏特化又叫局部特化或者部分特化也就是在特定的条件下使用特定的对象来替换模板参数但又不能完全替换
#include stack1.hpp// partial specialization of class Stack for pointers:
templatetypename T
class StackT* {private:std::vectorT* elems; // elementspublic:void push(T*); // push elementT* pop(); // pop elementT* top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();}
};templatetypename T
void StackT*::push (T* elem)
{elems.push_back(elem); // append copy of passed elem
}templatetypename T
T* StackT*::pop ()
{assert(!elems.empty());T* p elems.back();elems.pop_back(); // remove last elementreturn p; // and return it (unlike in the general case)
}templatetypename T
T* StackT*::top () const
{assert(!elems.empty());return elems.back(); // return copy of last element
}
使用
templatetypename T
class StackT*
{}我们定义了一个类模板
T仍然是模板参数,但是为了T*特化
具有多个参数的部分特化
templatetypename T1, typename T2
class MyClass
{ … };// partial specialization: both template parameters have same type
templatetypename T
class MyClassT,T
{ … };
// partial specialization: second type is int
templatetypename T
class MyClassT,int
{ … };// partial specialization: both template parameters are pointer types
templatetypename T1, typename T2
class MyClassT1*,T2*
{ … };MyClass int, float mif; // uses MyClassT1,T2
MyClass float, float mff; // uses MyClassT,T
MyClass float, int mfi; // uses MyClassT,int
MyClass int*, float* mp; // uses MyClassT1*,T2*如果有多个模板匹配,就会歧义:
MyClass int, int m; // ERROR: matches MyClassT,T and MyClassT,int
MyClass int*, int* m; // ERROR: matches MyClassT,T and MyClassT1*,T2*2.7 默认模板参数
对类模板,你可以设置一个默认模板参数,例如,对于Stack你可以设置一个默认模板参数管理内容
templatetypename T, typename Cont std::vectorT
class Stack {private:Cont elems; // elementspublic:void push(T const elem); // push elementvoid pop(); // pop elementT const top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();}
};templatetypename T, typename Cont
void StackT,Cont::push (T const elem)
{elems.push_back(elem); // append copy of passed elem
}templatetypename T, typename Cont
void StackT,Cont::pop ()
{assert(!elems.empty());elems.pop_back(); // remove last element
}templatetypename T, typename Cont
T const StackT,Cont::top () const
{assert(!elems.empty());return elems.back(); // return copy of last element
}
这个例子就是使用std::vector作为默认内容管理器
注意:这个类现在有两个模板参数,所以每个成员函数也应该有两个模板参数
int main()
{// stack of ints:Stackint intStack;// stack of doubles using a std::deque to manage the elementsStackdouble,std::dequedouble dblStack;// manipulate int stackintStack.push(7);std::cout intStack.top() \n;intStack.pop();// manipulate double stackdblStack.push(42.42);std::cout dblStack.top() \n;dblStack.pop();
}
这样Stackint intStack;使用std::vector管理元素,你也可以自定义管理器Stackdouble,std::dequedouble dblStack;
2.8 类型别名
为整个类型定义一个新名字让类模板更方便使用
通过使用using
using IntStack Stack int; // alias declaration
void foo (IntStack const s); // s is stack of ints
IntStack istack[10]; // istack is array of 10 stacks of intsusing IntStack Stack int;这样你就可以为整个类型定义一个别名,更方便使用
由于模板不是一个类型所以不能定义一个typedef引用一个模板但是新标准(since C11)允许使用using为类模板定义一个别名
以Stack使用std::deque管理元素为例
templatetypename T
using DequeStack StackT, std::dequeT;这样我们就可以使用DequeStackint 代替 Stackint,std::dequeint,这两个表达的完全相同
2.9 类模板参数推断
在C17之前,你必须传入所有的模板参数类型,但是,C17之后,这个限制放松了,如果能通过构造函数推断出模板参数类型,你可以不用明确的指定参数类型
Stack int intStack1; // stack of strings
Stack int intStack2 intStack1; // OK in all versions
Stack intStack3 intStack1; // OK since C17构造函数:
templatetypename T class Stack
{
private: std::vectorT elems; // elementspublic: Stack () default; Stack (T const elem) // initialize stack with one element: elems({elem}) { }…
};你可以这样声明一个Stack:
Stack intStack 0; // Stackint deduced since C17通过用整初始化Stack推断出模板参数T为int从而实例化一个Stackint
原则上也可以传递字符串字面值常量但这样会造成许多麻烦。用引用传递模板类型T的实参时模板参数不会decay最终得到的类型是原始数组类型
Stack stringStack bottom; // Stackchar const[7] deduced since C17传值的话则不会有这种问题模板实参会decay原始数组类型会转换为指针
templatetypename T
class Stack {public:Stack(T x) : v({x}) {}private:std::vectorT v;
};Stack stringStack bottom; // Stackconst char* deduced since C17传值时最好使用std::move以避免不必要的拷贝
templatetypename T
class Stack {public:Stack(T x) : v({std::move(x)}) {}private:std::vectorT v;
};**推断指引(**Deduction Guides)
如果构造函数不想使用传值方式声明,也有其他的解决办法,
你可以定义一个专用的类型指引,将C字符串推断为std::string
Stack( char const*) - Stackstd::string;这个指引必须出现在类定义的块,或者命名空间里
Stack(const char*) - Stackstd::string;
Stack stringStack{bottom}; // OK: Stackstd::string deduced since C17但由于语法限制,下面这种方法不可以
Stack stringStack bottom; // Stackstd::string deduced, but still not valid因为不能使用拷贝构造() 传递一个字符串去构造一个std::string.
你可以这样:
Stack stack2{stringStack}; // Stackstd::string deduced
Stack stack3(stringStack); // Stackstd::string deduced
Stack stack4 {stringStack}; // Stackstd::string deduced2.10 模板化聚合Templatized Aggregates
聚合类也能作为模板
templatetypename T
struct A {T x;std::string s;
};这样可以为了参数化值而定义一个聚合它可以像其他类模板一样声明对象同时当作一个聚合使用
Aint a;
a.x 42;
a.s initial value;C17中可以为聚合类模板定义deduction guide
templatetypename T
struct A {T x;std::string s;
};A(const char*, const char*) - Astd::string;int main()
{A a { hi, initial value };std::cout a.x; // hi
}没有deduction guide初始化就无法进行因为A没有构造函数来推断。std::array也是一个聚合元素类型和大小都是参数化的C17为其定义了一个deduction guide
namespace std {
templatetypename T, typename... U array(T, U...)- arrayenable_if_t(is_same_vT, U ...), T, (1 sizeof...(U));
}std::array a{ 1, 2, 3, 4 };
// 等价于
std::arrayint, 4 a{ 1, 2, 3, 4 };累死了,中秋还在肝… 参考解答: GotW #8: CHALLENGE EDITION: Exception Safety ↩︎ C17之后,如果参数类型可以从构造函数推断出来,可以跳过写模板参数即 ↩︎