第二章 类模板

栈类模板的构造与使用

#include <vector>
#include <cassert>
#include <iostream>

template<typename T>
class Stack {
private:
    std::vector<T> elems;
public:
    void push(const T& elem);
    T pop(); // 这里实现带返回值的pop(并不保证异常安全)
    const T& top() const;
    bool empty() const {
        return elems.empty();
    }
    Stack() = default;
    Stack(const Stack&);
    Stack& operator=(const Stack&);
};

template<typename T>
bool operator==(const Stack<T>& lhs, const Stack<T>& rhs);

template<typename T>
void Stack<T>::push(const T& elem) {
    elems.push_back(elem);
}

template<typename T>
T Stack<T>::pop() {
    assert(!elems.empty());
    T elem = elems.back();
    elems.pop_back();
    return elem;
}

template<typename T>
const T& Stack<T>::top() const {
    assert(!elems.empty());
    return elems.back();
}

int main() {
    Stack<int> t1;
    Stack<std::string> t2;
    t1.push(1); // 对 Stack<int>::push() 做实例化
    t1.push(2);
    t2.push("hello"); // 对 Stack<std::string>::push() 做实例化

    std::cout<<t2.top()<<std::endl; // 对 Stack<std::string>::top() 做实例化

    int tmp = t1.pop(); // 对 Stack<int>::pop() 做实例化
    std::cout<<tmp<<std::endl;

    // 其他类内函数,均没有被实例化
    return 0;
}

注意,编译器只会对会调用的函数进行实例化,对于类模板,只有在使用类内函数时,才会进行实例化,节省时间与空间。(如果 类模板具有静态成员,则对于使用类模板的每个类型实例,这些成员也会实例化一次。)

概念concept

一个模板类的实例化,不可避免地会调用一些必要函数(如构造函数等),然而,这些必要操作可能并没有被实现,比如:

#include <iostream>
#include <utility>
#include <vector>

template<typename T>
class A {
public:
    std::vector<T> arr;
    A() = default;
    void push(const T& data) {
        arr.push_back(data);
    }
    void printAll() const {
        for( const T& it : arr ) {
            std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
        }
    }
};

int main() {
    // 因为int有默认的<<操作符,所以可以正常输出
    A<int> a;
    for(int i = 0; i < 5; i++) {
        a.push(i);
    }
    a.printAll();   

    A<std::pair<int,int>> b;
    for(int i = 0; i < 5; i++) {
        b.push(std::make_pair(i,i));
    }
    b.printAll(); // 失败,因为pair 没有默认的<<操作符
    return 0;
}

这种情况,推荐使用C++20 中的concept,在编译阶段检查模板参数类型是否满足特定的属性或要求,增强了模板编程的灵活性和类型安全性。

类模板特化

需要为每个模板函数都实现一份特化版本:

#include <iostream>
#include <utility>
#include <vector>

template<typename T>
class A {
public:
    std::vector<T> arr;
    A() = default;
    void push(const T& data) {
        arr.push_back(data);
    }
    void printAll() const {
        for( const T& it : arr ) {
            std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
        }
    }
};

template<>
class A<std::pair<int, int>> {
public:
    std::vector<std::pair<int, int>> arr;
    A() = default;
    void push(const std::pair<int, int>& data) {
        arr.push_back(data);
    }
    void printAll() const {
            for( const std::pair<int,int>& it : arr ) {
                std::cout<<it.first<<":"<<it.second<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
            }
        }
};

int main() {
    // 因为int有默认的<<操作符,所以可以正常输出
    A<int> a;
    for(int i = 0; i < 5; i++) {
        a.push(i);
    }
    a.printAll();   

    A<std::pair<int,int>> b;
    for(int i = 0; i < 5; i++) {
        b.push(std::make_pair(i,2*i));
    }
    b.printAll(); // 调用的是特化模板,所以成功
    return 0;
}

注意,特化实现可以和主模板完全不同,比如,特化版的容器,完全可以不用vector,而用deque。

偏特化

特化(Full Specialization):完全替代原有的模板定义,适用于特定的模板参数组合。

偏特化(Partial Specialization):只替代模板定义的部分内容,适用于一类特定但不完全确定的模板参数组合。

上一节是对 pair<int,int> 的模板类特化,下面这个是对pair<T,T>的偏特化

#include <iostream>
#include <utility>
#include <vector>

template<typename T>
class A {
public:
    std::vector<T> arr;
    A() = default;
    void push(const T& data) {
        arr.push_back(data);
    }
    void printAll() const {
        for( const T& it : arr ) {
            std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
        }
    }
};

template<typename T>
class A<std::pair<T, T>> {
public:
    std::vector<std::pair<T, T>> arr;
    void push(const std::pair<T, T>& data) {
        arr.push_back(data);
    }
    void printAll() const {
            for( const std::pair<T,T>& it : arr ) {
                std::cout<<it.first<<":"<<it.second<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
            }
        }
};

int main() {
    // std::pair<int,int> 版本
    A<std::pair<int,int>> a;
    for(int i = 0; i < 5; i++) {
        a.push(std::pair<int,int>(i,i));
    }
    a.printAll(); 
	
	// std::pair<double,double> 版本
    A<std::pair<double,double>> b;
    for(int i = 0; i < 5; i++) {
        b.push(std::pair<double,double>(i*0.2,i*0.3));
    }
    b.printAll();
    return 0;
}

类型别名

可以通过typedef 或者 using(C++11) 来简化类模板的使用:

typedef std::pair<int,int> PAIR_INT;
using PAIR_DOUBLE = std::pair<double,double>;

A<PAIR_INT> a;
for(int i = 0; i < 5; i++) {
    a.push(std::pair<int,int>(i,i));
}
a.printAll(); 

A<PAIR_DOUBLE> b;
for(int i = 0; i < 5; i++) {
    b.push(std::pair<double,double>(i*0.2,i*0.3));
}
b.printAll(); 

模板聚合

也就是模板和特定类型混用,聚合类 (不由用户提供、显式或继承的构造函数的类/结构,没有 private 或 protected 的非静态 数据成员,没有虚函数,也没有 virtual、private 或 protected 基类) 也可以是模板。:

template<typename T> struct ValueWithComment {
T value;
std::string comment; };

// C++17以后可以定义推导策略
ValueWithComment(char const*, char const*) -> ValueWithComment<std::string>;
ValueWithComment vc2 = {"hello", "initial value"};

总结

• 类模板是在实现时保留一个或多个类型参数的类。
• 要使用类模板,需要将类型作为模板参数传递。并为这些类型,实例化 (并编译) 类模板。
• 对于类模板,只有调用的成员函数会实例化。
• 可以为某些类型特化类模板。
• 可以偏特化某些类型的类模板。
• C++17后,可以从构造函数中自动推导出类模板的参数。
• 可以定义聚合类模板。
• 若声明为按值调用,则模板类型的调用参数会衰变。
• 模板只能在全局/命名空间作用域或类声明内部声明和定义。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值