玩命加载中 . . .

16.1-定义模板


16.1.1 函数模板

定义一个通用的函数模板,用于两个对象的比较

template<typename T>    // 模板参数列表
int compare(const T& v1, const T& v2) {
    if (v1 < v2) return -1; // v1小于v2返回-1
    if (v2 < v1) return 1;  // v1大于v2返回1
    return 0;               // 相等返回0
}

T的实际类型在编译时根据compare的使用情况来确定

实例化函数模板

// 实例化出int compare(const int&, const int&)
cout << compare(1, 0) << endl;  // T为int

// 实例化出int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl;    // T为vector<int>

类型参数可以用来指定返回类型或函数的参数类型

template<typename T>
T foo(T* p) {
    T tmp = *p; // tmp的类型将是指针p指向的类型
    ...
    return tmp;
}

不能省略classtypename

// 错误,U之前必须加上clas或template
template<typename T, U> T calc(const T&, const U&);
// 正确,在模板参数列表中,typename和class是一样的
template<typename T, class U> T calc(const T&, const U&);

可以在模板中定义非类型参数,表示一个值而非一个类型

例如,数组不能传递维度,所以将模板参数定义为整型参数,让函数自动推断数组的维度

template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
    return strcpy(p1, p2);
}

compare("hi", "mom");
// int compare(const char (&p1)[3], const char (&p2)[4]);
// 加上字符串后面的'\0'

如果要加inlineconstexpr,需要放在模板参数列表后面

template<typename T> inline 
T min(const T&, const T&);

模板程序应该尽量减少对实参类型的要求

可以只用<运算符的话,就不要用>运算符,因为有些类可能只重载了<运算符,而没有重载>运算符

也可以调用标准库函数

template<typename T>
int compare(const T& v1, const T& v2) {
    if (less<T>()(v1, v2)) return -1;
    if (less<T>()(v2, v1)) return 1;
    return 0;
}

这样指针也能用

补充:

  • 静态内存或栈内存

分配在静态内存或栈内存中的由编译器自动创建和销毁

  • 静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。static对象在使用之前分配,在程序结束时销毁
  • 栈内存用来保存定义在函数内的非static对象。栈对象仅在其定义的程序块进行时才存在
  • 动态内存

程序用堆来存储动态分配的对象,就是那些在程序运行时分配的对象,动态对象的生存期由程序来控制,当动态对象不再使用时,我们的代码必须显式地销毁它们


16.1.2 类模板

template<typename T>
class Blob {
public:
    typedef T value_type;
    typedef typename vector<T>::size_type size_type;
    Blob();
    Blob(initializer_list<T> il);
    // Blob中的元素数量 
    size_type size() const { return data->size(); }
    // 添加和删除元素
    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:
    shared_ptr<vector<T>> data;
    void check(size_type i, const string& msg) const;
};

实例化模板

Blob<int> ia;   // 空的Blob
Blob<int> ia2 = {0, 1, 2, 3, 4};    // 有5个元素的Blob

类模板的成员函数

template<typename T>
T& Blob<T>::back() {
    check(0, "back on empty Blob"); // 没有元素报错
    return data->back();
}

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();
}

Blob构造函数

创建一个vector的对象,返回指向vector的指针,用来初始化data成员

template<typename T>
Blob<T>::Blob(): data(make_shared<vector<T>>()) {}

template<typename T>
Blob<T>::Blob(initializer_list<T> il): data(make_shared<vector<T>>(il)) {}

为了使用这个构造函数,我们必须传递给它一个initializer_list,其中的元素必须与Blob的元素类型兼容

Blob<string> articles = {"a", "an", "the"};
  • 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
  • 在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板参数

当我们在类模板外定义其成员时,我们并不在类的作用域中,直到遇到类名才表示进入类的作用域

// 重载后置++运算符
template<typename T> 
BlobPtr<T> BlobPtr<T>::operator++(int) {
    BlobPtr ret = *this;    // 等价于 BlobPtr<T> ret = *this;
    ++*this;
    return ret;
}

返回类型位于类的作用域之外,所以用加模板参数,函数体在类的作用域之内,所以不用加模板参数

类模板和友元

一对一友好关系

// 前置声明,在Blob中声明友元所需要的
template<typename T> class BlobPtr;
template<typename T> class Blob;    // 运算符==中的参数所需要的
template<typename T>
bool operator==(const Blob<T>&, const Blob<T>&);

template<typename T> class Blob {
    // 每个Blob实例将访问权限授予相同类型实例化的BlobPtr和相等运算符
    friend class BlobPtr<T>;
    friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
};

Blob<char> ca;  // BlobPtr<char>和operator==<char>都是本对象的友元
Blob<int> ia;   // BlobPtr<int>和operator==<int>都是本对象的友元

T类型不同的就不是友元了

通用和特定的模板友好关系

先看一种情况

template<typename T> class Pal;
class C {
    friend class Pal<C>;
    template<typename T> friend class Pal2;
};

C是一个普通的非模板类,用类C去实例化的Pal<C>类型是C的友元,所以这里需要先声明Pal,一对一的关系

Pal2的所有类型都是C的友元,无需前置声明,一对多的关系

再看另一种情况

template<typename T> class Pal;
template<typename T> class C2 {
    friend class Pal<T>;
    template<typename X> friend class Pal2;
    friend class Pal3;
};

C2本身是一个模板类,与C2实例类型相同的Pal才是C2实例的友元,需要前置声明,是一对一的关系

Pal2的所有类型都是C2的所有实例的友元,多对多的关系

Pal3是非模板类,他是C2所有实例的友元,多对一的关系

  • 为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数

令模板自己的类型参数成为友元

template<typename Type> class Bar {
    friend Type;    // 将访问权限授予用来实例化Bar的类型
};

模板类型别名

template<typename T> using twin = pair<T, T>;
twin<string> authors;   // pair<string, string>

可以固定一个或多个模板参数

template<typename T> using partNo = pair<T, unsigned>;
partNo<string> book;    // pair<string, unsigned>

类模板的static成员

template<typename T> class Foo {
public:
    static size_t count() { return ctr; }
private:
    static size_t ctr;
};

Foo<string> fs;
// 实例化static成员Foo<string>::ctr和Foo<string>::count()

可以将static成员定义为模板

template<typename T>
size_t Foo<T>::ctr = 0; // 定义并初始化ctr

通过类来访问static成员,必须引用一个特定的类型

Foo<int> fi;    // 实例化Foo<int>和static成员ctr
auto ct = Foo<int>::count();    // 实例化Foo<int>::count()
ct = fi.count();    // 使用Foo<int>::count()
ct = Foo::count();  // 错误,不知道是哪个实例的count

16.1.3 模板参数

作用域

typedef double A;
template<typename A, typename B>
void f(A a, B b) {
    A tmp = a;    // tmp的类型为A的类型,不是double
    double B;   // 错误,重声明模板参数
}

函数作用域里面的A是模板参数,隐藏了外层的typedef

template<typename V, typename V>    // 错误,非法重用模板参数名V

与函数参数相同,声明中的模板参数的名字不必与定义中相同

使用类的类型成员

如果希望使用一个模板参数的类型成员,就必须显式地告诉编译器该名字是一个类型

template<typename T>
typename T::value_type top(const T& c) {
    if (!c.empty()) 
        return c.back();
    else 
        return typename T::value_type();
}

这里T可能是一个容器vector<int>,而要返回是一个元素int,所以需要显式说明返回值是T里面的元素类型

如果不加typename,则类名+作用域运算符默认是访问类的成员,而不是类型

当我们希望通知编译器一个名字表示类型时,必须使用typename,而不能使用class

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;
}

bool i = compare(0, 42);    // 使用less<int>
Sales_date item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);    // 自定义的比较函数

只要有一个默认实参,则剩下的所有参数都要有默认实参


16.1.4 成员模板

一个类可以包含本身是模板的成员函数,称为成员模板,不能是虚函数

普通(非模板)类的成员模板

class DebugDelete {
public:
    DebugDelete(ostream& s = cerr): os(s) {}
    template<typename T> void operator()(T* p) const {
        os << "deleting unique_ptr" << endl;
        delete p;
    }
private:
    ostream& os;
};

double *p = new double;
DebugDelete d;
d(p);   // 调用operator(double*),释放double型指针
int *ip = new int;
DebugDelete()(ip);  // 使用临时对象调用operator(int*),释放int型指针

DebugDelete中重载括号运算符是一个成员模板,不管输入的是什么类型,都可以delete

类模板的成员模板

如果类和成员函数都是模板,要先写类的模板参数,再写成员函数的模板参数

template<typename T> class Blob {
    template<typename It> Blob(It b, It e);
    // ...
};

template<typename T>    // 类的类型参数
template<typename It>   // 构造函数的类型参数
Blob<T>::Blob(It b, It e): data(make_shared<vector<T>>(b, e)) {}

实例化与成员模板

int ia[] = {0, 1, 2, 3, 4, 5};
Blob<int> a1(begin(ia), begin(ia)); // 实例化Blob<int>以及接受两个int*参数的构造函数

vector<long> vi = {0, 1, 2, 3, 4, 5};
Blob<long> a2(vi.begin(), vi.end()); // 实例化Blob<long>以及接受两个vector<long>::iterator参数的构造函数

list<const char*> w = {"now", "is", "the", "time"};
Blob<string> a3(w.begin(), w.end());    // 实例化Blob<string>以及接受两个list<const char*>::iterator参数的构造函数

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录