在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
《C++ Template》对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值)。 template <typename T> inline T const& max(T const& a, T const& b) { return a < b ? : b a; } 注:你可以使用任何类型(基本类型、类等)来实例化该类型参数,只要所用类型提供模板使用的操作就可以。 max(32, 43); 这种用具体类型代替模板参数的过程叫做实例化,它产生了一个模板的实例。 注:通常而言,并不是把模板编译成一个可以处理任何类型的单一实体;而是对于实例化模板参数的每种类型,都从模板产生一个不同的实体。 于是,我们可以得出一个结论:模板被编译了两次,分别发生在: 2.2 实参的演绎(deduction) 注:模板实参不允许进行自动类型转换;每个T都必须正确地匹配。如: max(4, 4.3); // Error:第1个参数类型是int,第2个参数类型是double 2.3 模板参数 template <typename T> // T是模板参数 (2)调用参数:位于函数模板名称之后,在一对圆括号内部进行声明: ...max(T const& a, T const& b); // a和b都是调用参数 注:由于函数模板历史发展过程中的一个失误,导致目前(2016/1/11)我们不能在函数模板内部指定缺省的模板实参(形如“template <typename T = xxx>”,不能指定xxx;)。但依然可以指定函数模板的调用实参(形如“...max(T const& a, T const& b = yyy)”,可以指定yyy)(以后应该会支持函数模板指定缺省模板实参)。 注:切记“模板参数”和“模板实参”的区别;函数的“模板参数”和“调用参数”、“模板实参”和“调用实参”的区别;“类型参数”和“非类型参数”的区别[参见下面第4章内容]; 函数模板和类模板区别【重要】:
(3)显式实例化:当模板参数和调用参数没有发生关联,或者不能由调用参数来决定模板参数的时候(例如:返回类型),在调用时就必须显式指定模板实参。切记,模板实参演绎并不适合返回类型。如下: template <typename T1, typename T2, typename RT> inline RT max(T1 const& a, T2 const& b); 那么必须进行显式实例化: max<int, double, double>(4, 4.3); // OK,但很麻烦。这里T1和T2是不同的类型,所以可以指定两个不同类型的实参4和4.3 注:通常而言,你必须指定“最后一个不能被隐式演绎的模板实参之前的”所有实参类型。上面的例子中,改变模板参数的声明顺序,那么调用者就只需要指定返回类型: template <typename RT, typename T1, typename T2> inline RT max(T1 const& a, T2 const& b); ... max<double>(4, 4.3); // ok,返回类型是double 2.4 重载函数模板
max<>(7, 42); // call max<int>(通过实参演绎)
(需要参考书本2.4节p17,p18例子,重点研究如下的传参方式): // 求两个指针所指向值的最大者 // T* const& a :表示 T类型的指针常量a(a指针的指向不可修改),并且指针常量a是通过引用方式传参;
3.1.1 类模板的声明 template <typename T> //可以使用class代替typename class Stack { ... }; 注:这个类的类型是Stack<T>,其中T是模板参数。因此,当在声明中需要使用该类的类型时,你必须使用Stack<T>。然而,当使用类名而不是类的类型时,就应该只用Stack;譬如,当你指定类的名称、类的构造函数、析构函数时,就应该使用Stack。 3.1.2 成员函数的实现 template <typename T> void Stack<T>::push(T const& elem) { elems.push_back(elem); } 显然,对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面,如: template <typename T> class Stack { ... void push(T const& elem) // 隐式内联 { elems.push_back(elem); } ... }; 3.2 类模板Stack的使用 为了使用类模板对象,必须显式地指定模板实参。 Stack<in> intStack; // 必须显式指定模板实参int
3.3 类模板的特化 template<> class Stack<std::string> { ... }; 进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代。如: void Stack<std::string>::push(std::string const& elem) { elems.push_back(elem); } 3.4 局部特化(偏特化) 例子如下: // 两个模板参数具有相同的类型 template <typename T> class Myclass<T, T> // { }; // 第2个模板参数的类型是int template <typename T> class Myclass<T, int> { }; // 也是一种模板特化,两个模板参数都是指针类型 template <typename T1, typename T2> class Myclass<T1*, T2*> // 也可以使引用类型T&,常引用等 { }; 3.5 缺省模板实参 template <typename T, typename CONT = std::vector<T> > class Stack { };
------------------------------------------------------------------------------------------------------------ // 非类型的类模板参数 int MAXSIZE :这个模板参数接受的不是一个类型,而是一个值(非类型) 注:每个模板实例都具有自己的类型,因此int20Stack和int40Stack属于不同的类型,而且这两种类型之间也不存在显式或者隐式的类型转换;所以它们之间不能互相替换,更不能互相赋值。 4.2 非类型的函数模板参数 template <typename T, int VAL> T addValue(T const& x) { return x + VAL; } 借助标准模板库(STL)使用上面例子: std::transform(source.begin(), source.end(), dest.begin(), addValue<int, 5>); 注:
std::transform(source.begin(), source.end(), dest.begin(), (int(*)(int const&))addValue<int, 5>); 4.3 非类型模板参数的限制
注:
template <char const* name> class MyClass { }; MyClass<"hello"> x; // ERROR:不允许使用字符文字"hello"
template <typename T> class MyClass { // 这里的typename被用来说明:T::SubType是定义于类T内部的一种类型 typename T::SubType* ptr; ... }; 注:本节同时提到了一个奇特的构造“.template”:只有当该前面存在依赖于模板参数的对象时,我们才需要在模板内部使用“.template”标记(和类似的诸如->template的标记),而且这些标记也只能在模板中才能使用。如下例子: void printBitset(std::bitset<N> const& bs) { // 如果没有使用这个template,编译器将不知道下列事实:bs.template后面的小于号(<)并不是数学 // 中的小于号,而是模板实参列表的起始符号。只有在编译器判断小于号之前,存在依赖于模板参数的构造 // 才会出现这种问题。在这个例子中,传入参数bs就是依赖于模板参数N的构造 std::cout << bs.template to_string<char, char_traits<char>, allocator<char> >(); } 5.2 使用this-> template <typename T> class Base { public: void exit(); }; template <typename T> class Derived : Base<T> // 模板基类 { public: void foo() { exit(); // (1)调用外部的exit()或者出现错误,而不会调用模板基类的exit(),下面解析。 } }; 注意:(C++ template 5.2&9.4.2)
5.3 成员模板 template <typename T> class Stack { ... 参见博文Effective C++ —— 模板与泛型编程(七) 条款45 运用成员函数模板接受所有兼容类型 5.4 模板的模板参数 template <typename T, template <typename ELEM, typename ALLOC = std::allocator<ELEM> > class CONT = std::deque > // 为模板的模板参数提供模板实参缺省值std::deque class Stack { ... 注:
5.5 零初始化 // 函数模板 template <typename T> void foo() { T x = T(); // 如果T是内建类型,x是0或者false }; // 类模板:初始化列表来初始化模板成员 template <typename T> class MyClass { private: T x; public: MyClass() : x() {} // 确认x已被初始化,内建类型对象也是如此 }; 5.6 使用字符串作为函数模板的实参 #include <string> // 注意,method1:引用参数 template <typename T> inline T const& max(T const& a, T const& b) { return a < b ? b : a; } // method2:非引用参数 template <typename T> inline T max2(T a, T b) { return a < b ? b : a; } int main() { std::string s; // 引用参数 ::max("apple", "peach"); // OK, 相同类型的实参 ::max("apple", "tomato"); // ERROR, 不同类型的实参 ::max("apple", s); // ERROR, 不同类型的实参 // 非引用参数 ::max2("apple", "peach"); // OK, 相同类型的实参 ::max2("apple", "tomato"); // OK, 退化(decay)为相同类型的实参 ::max2("apple", s); // ERROR, 不同类型的实参 } 上面method1的问题在于:由于长度的区别,这些字符串属于不同的数值类型。也就是说,“apple”和“peach”具有相同的类型char const[6];然而“tomato”的类型则是char const[7]。
template <typename T, int N, int M> T const* max (T const (&a)[N], T const (&b)[M]) { return a < b ? b : a; } 5. 强制要求应用程序程序员使用显式类型转换。
------------------------------------------------------------------------------------------------------------ 大多数C和C++程序员会这样组织他们的非模板代码:
这样一切都可以正常运作了。所需的类型定义在整个程序中都是可见的;并且对于变量和函数而言,链接器也不会给出重复定义的错误。 // ----------------------------------------------------------- //basics/myfirst.hpp #ifndef MYFIRST_HPP #define MYFIRST_HPP // 模板声明 template <typename T> void print_typeof(T const&) #endif // MYFIRST_HPP // ----------------------------------------------------------- //basics/myfirst.cpp // 使用模板 int main() { double ice = 3.0; print_typeof(ice); // 调用参数类型为double的函数模板 } 大多数C++编译器都会顺利地接受这个程序;但是链接器可能会报错,提示找不到函数print_typeof()的定义。 事实上,这个错误的原因在于:
要解决上面的问题,可以从两个点入手: (1)解决找不到函数模板定义问题(包含模型); (2)解决没有指出“编译器必须基于(哪个)特定实参对所包含的模板定义进行实例化”问题(显示实例化)。 6.1.2 头文件中的模板 对于前面的问题,我们通常是采取对待宏或内联函数的解决方法:我们把模板的定义也包含在声明模板的头文件里面,即让定义和声明都位于同一个头文件中。我们称模板的这种组织方式为包含模型。针对包含模型的组织方式,我们可以得出:包含模型明显增加了包含头文件myfirst.hpp的开销。 从包含模型得出的另一个结论是:非内联函数模板与“内联函数和宏”有一个很重要的区别,那就是非内联函数模板在调用的位置并不会被扩展,而是当它们基于某种类型进行实例化之后,才产生一份新的(基于该类型的)函数拷贝(所以对于非内联函数模板而言,实例化之后才能确定为一个针对特定类型的函数)。 最后,我们需要指出的是:在我们的例子中应用到普通函数模板的所有特性,对类模板的成员函数和静态数据成员、成员函数模板也都是适用的。 6.2 显式实例化 6.2.1 显式实例化的例子 //basics/myfirstinst.cpp #include "myfirst.cpp" // 基于类型double显式实例化print_typeof() template void print_typeof<double>(double const&); 显式实例化指示符由关键字template和紧接其后的我们所需要实例化的实体(可以是类、函数、成员函数等)的声明组成,而且,该声明是一个已经用实参完全(注意,是完全)替换参数之后的声明。该指示符也适用于成员函数和静态数据成员,如: // 基于int显式实例化MyClass<>的构造函数 template MyClass<int>::MyClass(); // 基于int显式实例化函数模板max() template int const& max(int const&, int const&); 你还可以显式实例化类模板,这样就可以同时实例化它的所有类成员。但有一点需要注意:对于那些在前面已经实例化过的成员,就不能再次对它们进行实例化(针对每个不同实体,不能存在多个显式实例化体,同时显式实例化体和模板特化也只能二者选其一)。 // 基于int显式实例化类Stack<> template class Stack<int> // 实例化它的所有类成员 // 错误,对于int,不能再次对它进行显式实例化 template Stack<int>:::Stack(); // 基于string显式实例化Stack<>的某些成员函数 template Stack<std::string>::Stack(); template void Stack<std::string>::push(std::string const&); template std::string Stack<std::string>::top() const; 注意:人工实例化有一个显著的缺点:我们必须仔细跟踪每个需要实例化的实体。对于大项目而言,这种跟踪会带来巨大负担,因此,我们并不建议使用这种方法。其优点在于,显式实例化可以精确控制模板实例的准确位置。 6.2.2 整合包含模型和显式实例化 // stack.hpp #ifndef STACK_HPP #define STACK_HPP #include <vector> template <typename T> class Stack { private: std::vector<T> elems; public: Stack(); void push(T const&); void pop(); T top() const; }; #endif // stackdef.hpp #ifndef STACKDEF_HPP #define STACKDEF_HPP #include "stack.hpp" template <typename T> void Stack<T>::push(T const& elem) { elems.push_back(elem); } ... #endif // stacktest1.cpp // 注意,这里和前面链接器报错的例子不同,这里是包含进了stackdef.hpp, // 这个文件里面含有函数的定义,所以不会产生链接器找不到的错误(其实在编译器中就已经能找到函数模板的定义了) #include "stackdef.hpp" // 书中是“stack.hpp”,应该有误 #include <iostream> #include <string> int main() { Stack<int> intStack; intStack.push(42); } // stack_inst.cpp #include "stack.hpp" // 书中是“stackdef.hpp”,应该有误 #include <string> template Stack<int>; template Stack<std::string>::Stack(); template void Stack<std::string>::push(std::string const&); template std::string Stack<std::string>::top() const; 6.3 分离模型 6.3.1 关键字 export // basics/myfirst3.hpp #ifndef MYFIRST_HPP #define MYFIRST_HPP // 模板声明 export template <typename T> void print_typeof(T const&); #endif // MYFIRST_HPP 注:
export template <typename T> class MyClass { public: void memfun1(); // 被导出的函数 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论