C++ 泛型与模板编程基础
一,模板函数
简单的例子
template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; }
在声明模板时需要以template关键字开头,紧接模板参数。模板参数可以分为类型参数与非类型参数,typename在作为类型参数的申明时与关键字class等价。
一个使用了非类型参数的例子
/* * 一个非类型参数可以是一个 整型,一个指向对象或者函数的指针,或左值引用。 * 绑定到指针或引用的实参必须拥有 静态的生存期 * 在模板的定义内,非类型模板参数是一个 常量值,在需要使用常量值的地方可以使用非类型模板参数,如数组的大小 */ template <unsigned N, unsigned M> int compare(const char (&p1)[N], const char (&p2)[M]) { return strcmp(p1, p2); }
上面的代码使用了N,M作为非类型参数,在模板中整型的非类型参数跟枚举类型或者const int差不多,可以作为数组的维度,如果像上面的例子一样写成数组的引用,编译器会自己推断出M,N。
更进一步的可以将两个例子放在同一个作用域实现函数重载。但是由于模板参数是推断出来的,容易出现二义性。具体参数匹配规则在后面补充(太尼玛容易忘记了)
int main() { //compare(9,4)不会二义性错误,而compare("123,"456)会出现二义性调用,所以很多错误都是在实例化模板的时候发现的 cout << compare(9, 4) ; //使用第一个模板函数 cout << compare("123", "456") << endl;//二义性错误 //模板直到实例化时才会生成代码,这一特性影响了我们发现编译错误的时机,编译器会在三个阶段报错: (1) 编译模板 (2)编译使用模板的代码 (3)实例化模板 return 0; }
模板也可以有默认实参,同时模板函数也可以拥有默认实参。
比如下面的代码
//在c++11中,不仅可以为模板提供默认实参,还可以为函数提供默认实参 /* 为函数提供了默认实参的版本*/ template <typename T, typename F = less<T>> int compare(const T &v1, const T &v2, F f = F()){ if (f(v1, v2)) return -1; if (f(v2, v1)) return 1; return 0; } /* 没有为函数提供默认实参*/ template <typename T, typename F = less<T>> int compare(const T &v1, const T &v2, F f) { if (f(v1, v2)) return -1; if (f(v2, v1)) return 1; return 0; }
另外值得注意的是,上面的函数的第三个参数就是所谓的可调用对象,其实就是个非常普通的重载了()运算符的对象,理解运算符就是一堆有优先级的函数之后就非常好理解所谓的可调用对象了,哼哼
此外,上面第一段代码中的函数默认参数
int compare(const T &v1, const T &v2, F f = F())
F f = F(),是指类型为F,形参取名f,默认值是F(),即对F使用默认构造函数构造出来的一个对象。
二,模板类
模板也可以用来声明定义类
像这样:
template <typename T> class Blob { public: typedef typename std::vector<T>::size_type size_type; //构造函数 Blob(); Blob(std::initializer_list<T> il); //Blob中元素数目 size_type size() const { return data->size(); } bool empty() const { return data->empty(); } //添加删除元素 void push_back(const T &t) { data->push_back(t); } void push_back(T &&t) { data->push_back(std::move(t)); } void pop_back(); //元素访问 T& back(); T& operator[](size_type i); private: std::shared_ptr<std::vector<T>> data; //若data[i]无效,则抛出msg void check(size_type i, const std::string &msg) const; }; //在类内部定义的函数会被默认声明为内联函数,在类外部定义的成员也需要使用template关键字 template<typename T> void Blob<T>::check(size_type i, const std::string &msg) const { if (i >= data->size()) { throw std::out_of_range(msg); } } template <typename T> T& Blob<T>::operator[](size_type i) { check(i, "subscript out of range"); return (*data)[i]; } template <typename T> void Blob<T>::pop_back() { check(0, "pop_back on empty Blob"); data->pop_back(); } template <typename T> Blob<T>::Blob() : data(std::make_shared<std::vector<T>>()) {} template <typename T> Blob<T>::Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) {}
注意,模板的声明与定义必须写在同一个文件中,模板并不支持分离编译。
通常使用类模板的时候必须提供模板实参,但是在类的作用域中,可以直接使用模板名而不提供实参。
此外,当类似
template<typename T> class A{ .. void f() { A::x *p; } .. };
的代码出现时,编译器无法判定A::x是模板类中声明的一个类型还是A::中的static变量x与p相乘。当出现类似这种情况时,编译器优先假定A::x不是类型声明,当使用模板类的类型成员时,我们必须显示的写成
typename A::x *p;
模板类的友元声明:
template <typename T> class Pal; class C { //将某特定类型的模板类声明为友元时需要前置声明 friend class Pal<C>; //将某个模板类的所有实例都声明为友元时,不需要前置声明 template <typename T> friend class Pal2; }; //模板类的情况类似 template <typename T> class C2 { friend class Pal<T>; //需要前置声明 template <typename X> friend class C2; //不需要前置声明 friend class Pal3; //Pal3是一个普通的类,不需要前置声明 friend T; // 新标准允许将T声明为友元 };
在c++ Primer中,完整的定义了两个模板类Blob与BlobPtr,实现了一个共享底层资源的资源管理类Blob,以及该类对应的迭代器BlobPtr,虽然是简单实现但是代码依旧有很多可以学习的地方
#ifndef BLOB_H #define BLOB_H //通常模板定义在头文件中,并且需要把声明与定义写在一起 //Blob是一个资源管理类,并且在复制时只进行浅拷贝,复制两个对象将管理同一份底层数据 #include <iterator> #include <string> #include <vector> #include <initializer_list> #include <cstddef> #include <stdexcept> #include <utility> #include <memory> #include <algorithm> #include <iostream> #include <cstdlib> #include <stdexcept> // forward declarations needed for friend declarations in Blob template <typename> class BlobPtr; template <typename> class Blob; // needed for parameters in operator== template <typename T> bool operator==(const Blob<T>&, const Blob<T>&); template <typename T> class Blob { // each instantiation of Blob grants access to the version of // BlobPtr and the equality operator instantiated with the same type friend class BlobPtr<T>; friend bool operator==<T> (const Blob<T>&, const Blob<T>&); public: //类内部的类型声明通常放在最前面,如果类内部使用了声明在类外部的类型之后,再在类内部声明一个同名类型,这次声明将报错 typedef T value_type; typedef typename std::vector<T>::size_type size_type; // constructors Blob(); Blob(std::initializer_list<T> il); template <typename It> Blob(It b, It e); Blob(T*, std::size_t); // return BlobPtr to the first and one past the last elements // 返回两个对应位置的'迭代器‘ BlobPtr<T> begin() { return BlobPtr<T>(*this); } BlobPtr<T> end() { auto ret = BlobPtr<T>(*this, data->size()); return ret; } // number of elements in the Blob size_type size() const { return data->size(); } bool empty() const { return data->empty(); } // add and remove elements //push与pop虽然不会改data的值,但是会改变*data的值,不应该声明const版本的push与pop //若一个Blob对象被声明为const,Blob就应该不能改变data所指对象的值才符合常理 void push_back(const T &t) {data->push_back(t);} void push_back(T &&t) { data->push_back(std::move(t)); } //声明了右值版本,提升效率 void pop_back(); // element access //访问接口的返回值应该是引用,才符合正常的使用习惯 T& front(); T& back(); T& at(size_type); //声明了const版本的访问接口,并且访问接口的返回值也应该是const的,理由同不定义const版本的pop与push一样 //若一个Blob对象是const的,我们不应该能通过该对象改变*data中元素的值 const T& back() const; const T& front() const; const T& at(size_type) const; T& operator[](size_type i); const T& operator[](size_type i) const; //一个设计良好的资源管理类需要有swap自定义的swap函数 //这个类并没有copy构造函数,在进行cpoy时只进行浅拷贝 void swap(Blob &b) { data.swap(b.data); } /* 这里将swap定义为成员函数,对于也许会使用stl中的泛型算法的类,通常会将swap定义为友元, * 使得在stl库中的泛型算法能够使用自定义版本的swap,在stl的泛型算法中,不要求类自己实现成员函数swap,甚至在 * 没有特定版本的swap(T&,T&)时,也能够调用stl::swap模板函数 * * void swap(A &a, A &b); * class A{ * friend swap(A &a, A &b) * }; * * void swap(A &a, A &b){ * using std::swap; * swap(a.memberA, b.memberA); * ... * } * 上述代码使用using std::swap,从std命名空间引入了模板函数swap,同时,如果decltype(a.member)有自己定义的swap函数 * 将会和std::swap一起被加入到重载函数候选集中,同时编译器会选择自定义版本的swap */ private: //作为直接的资源管理者应该使用share_ptr,而非weak_ptr std::shared_ptr<std::vector<T>> data; // throws msg if data[i] isn't valid void check(size_type i, const std::string &msg) const; }; // constructors template <typename T> Blob<T>::Blob(T *p, std::size_t n): data(std::make_shared<std::vector<T>>(p, p + n)) { } template <typename T> Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { } template <typename T> // type parameter for the class template <typename It> // type parameter for the constructor Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) { } template <typename T> Blob<T>::Blob(std::initializer_list<T> il): data(std::make_shared<std::vector<T>>(il)) { } // check member template <typename T> void Blob<T>::check(size_type i, const std::string &msg) const { if (i >= data->size()) throw std::out_of_range(msg); } // element access members template <typename T> T& Blob<T>::front() { // if the vector is empty, check will throw check(0, "front on empty Blob"); return data->front(); } template <typename T> T& Blob<T>::back() { check(0, "back on empty Blob"); return data->back(); } template <typename T> void Blob<T>::pop_back() { check(0, "pop_back on empty Blob"); data->pop_back(); } template <typename T> const T& Blob<T>::front() const { check(0, "front on empty Blob"); return data->front(); } template <typename T> const T& Blob<T>::back() const { check(0, "back on empty Blob"); return data->back(); } template <typename T> T& Blob<T>::at(size_type i) { // if i is too big, check will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; // (*data) is the vector to which this object points } template <typename T> const T& Blob<T>::at(size_type i) const { check(i, "subscript out of range"); return (*data)[i]; } template <typename T> T& Blob<T>::operator[](size_type i) { // if i is too big, check will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; } template <typename T> const T& Blob<T>::operator[](size_type i) const { check(i, "subscript out of range"); return (*data)[i]; } // operators template <typename T> std::ostream& operator<<(std::ostream &os, const Blob<T> a) { os << "< "; for (size_t i = 0; i < a.size(); ++i) os << a[i] << " "; os << " >"; return os; } template <typename T> bool operator==(const Blob<T> lhs, const Blob<T> rhs) //默认拷贝是浅拷贝,所以是不是const Blob<T> &区别并不大 { if (rhs.size() != lhs.size()) return false; for (size_t i = 0; i < lhs.size(); ++i) { if (lhs[i] != rhs[i]) return false; } return true; } // BlobPtr throws an exception on attempts to access a nonexistent element template <typename T> bool operator==(const BlobPtr<T>&, const BlobPtr<T>&); //模板类也能继承 template <typename T> class BlobPtr : public std::iterator<std::bidirectional_iterator_tag,T> { friend bool operator==<T>(const BlobPtr<T>&, const BlobPtr<T>&); public: BlobPtr(): curr(0) { } BlobPtr(Blob<T> &a, size_t sz = 0): wptr(a.data), curr(sz) { } T &operator[](std::size_t i) { auto p = check(i, "subscript out of range"); return (*p)[i]; // (*p) is the vector to which this object points } const T &operator[](std::size_t i) const { auto p = check(i, "subscript out of range"); return (*p)[i]; // (*p) is the vector to which this object points } T& operator*() const { auto p = check(curr, "dereference past end"); return (*p)[curr]; // (*p) is the vector to which this object points } T* operator->() const { // delegate the real work to the dereference operator return & this->operator*(); } /* * ->执行过程: p->member; * (1)如果p是原生指针,那么等价于(*p).member,注意‘.’运算符是不能被重载的 * (2)如果p是自定义类型,那么等价于(p.operator->())->member * 之后如果p.operator->()返回的是自定义类型,则继续迭代(2),若是原生指针,则按照(1)解释 */ // increment and decrement BlobPtr& operator++(); // prefix operators BlobPtr& operator--(); BlobPtr operator++(int); // postfix operators BlobPtr operator--(int); private: // check returns a shared_ptr to the vector if the check succeeds std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const; // store a weak_ptr, which means the underlying vector might be destroyed std::weak_ptr<std::vector<T>> wptr; std::size_t curr; // current position within the array }; // equality operators template <typename T> bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) { return lhs.wptr.lock().get() == rhs.wptr.lock().get() && lhs.curr == rhs.curr; } template <typename T> bool operator!=(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) { return !(lhs == rhs); } // check member template <typename T> std::shared_ptr<std::vector<T>> BlobPtr<T>::check(std::size_t i, const std::string &msg) const { auto ret = wptr.lock(); // is the vector still around? if (!ret) throw std::runtime_error("unbound BlobPtr"); if (i >= ret->size()) throw std::out_of_range(msg); return ret; // otherwise, return a shared_ptr to the vector } // member operators // postfix: increment/decrement the object but return the unchanged value template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int) { // no check needed here; the call to prefix increment will do the check BlobPtr ret = *this; // save the current value ++*this; // advance one element; prefix ++ checks the increment return ret; // return the saved state } template <typename T> BlobPtr<T> BlobPtr<T>::operator--(int) { // no check needed here; the call to prefix decrement will do the check BlobPtr ret = *this; // save the current value --*this; // move backward one element; prefix -- checks the decrement return ret; // return the saved state } // prefix: return a reference to the incremented/decremented object template <typename T> BlobPtr<T>& BlobPtr<T>::operator++() { // if curr already points past the end of the container, can't increment it check(curr, "increment past end of BlobPtr"); ++curr; // advance the current state return *this; } template <typename T> BlobPtr<T>& BlobPtr<T>::operator--() { // if curr is zero, decrementing it will yield an invalid subscript --curr; // move the current state back one element check(-1, "decrement past begin of BlobPtr"); return *this; } #endif
浙公网安备 33010602011771号