模板元编程之类模板(二)

一、类模板Stack的实现

复制代码
template <typename T>
class Stack {
private:
    std::vector<T> elems; //存储元素的容器

public:
    void push(T const&); //压入元素
    T pop(); //弹出元素
    T top() const; //返回栈顶元素  
};

template <typename T>
void Stack<T>::push(T const& elem) {
    elems.push_back(elem); //把elems的拷贝附加到末尾
}

template <typename T>
T Stack<T>::pop() {
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::pop():empty stack");
    }
  T elem = elems.back()
  elems.pop_back(); return elem; } template
<typename T> T Stack<T>::top() const { if (elems.empty) { throw std::out_of_range("Stack<>::pop():empty stack"); } return elems.back(); //返回最后一个元素的拷贝 }
复制代码

二、类模板的声明

类模板的声明和函数模板的声明很类似:

template <typename T> 
class Stack {
    ...
};

另外,可以再次使用关键字class来代替typename:

template <class T>
class Stack {
    ...
};

在类模板的内部,T可以像其他任何类型一样,用于声明成员变量和成员函数,在下面的示例中,T被用于声明vector的元素类型,声明push()是一个接收常量T引用为唯一实参的成员函数,声明top是返回类型为T的成员函数:

复制代码
template <typename T>
class Stack {
private:
    std::vector<T> elems; //存储元素的容器

public:
    void push(T const&); //压入元素
    void pop(); //弹出元素
    T top() const; //返回栈顶元素
};
复制代码

这个类的类型是Stack<T>,其中T是类模板参数,因为,在声明中需要使用该类的类型时,必须使用Stack<T>。例如,如果你要声明自己实现的拷贝构造函数和赋值运算符,那应该如下述示例:

template <typename T>
class Stack {
    ...
    Stack(Stack<T> const&); //拷贝构造函数
    Stack<T>& operator=(Stack<T> const&); //赋值运算符
};

但是,但使用类名而不是类的类型时,就应该只用Stack,比如,当指定类的名称,类的构造函数,析构函数时,就应该使用Stack。

为了定义类模板的成员函数,必须指定该成员函数是一个函数模板,而且还需要使用这个类模板的完整类型限定符,因为类型Stack<T>的成员函数push()的实现如下:

template <typename T>
void Stack<T>::push(T const& elem) {
    elems.push_back(elem); //把elemd的拷贝附加到末尾
}

三、类模板Stack的使用

为了使用类模板对象,必须显式的指定模板实参,下面示例展示了如何使用类模板Stack<>:

复制代码
int main() {
    Stack<int> intStack; //元素类型为int的栈
    Stack<std::string> stringStack; //元素类型为字符串的栈

    //使用int栈
    intStack.push(10);
    std::cout << intStack.top() << std::endl;
    
    //使用string栈
    stringStack.push("hello");
    std::cout << stringStack.top() << std::endl;
    stringStack.pop();
    stringStack.pop();
    
    return 0;
}
复制代码

注意,只有那些被调用的成员函数,才会产生这些函数的实例化代码,对于类模板,成员函数只有在被使用的时候才会被实例化,,显然这样可以节省空间和时间。如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。

四、类模板的特化

4.1 完全特化

可以使用模板实参来特化类模板,和函数模板的重载类似,通过特化类模板,可以优化基于某种特定类型的实现。或者客户某种特定类型在实例化类模板时所出现的不足,如果要实例化一个类模板,还要特化该类模板的所有成员函数。

template<>
class Stack<std::string> {
    ...
   // string类型的特化实现 };

4.2 局部特化

类模板也可以局部特化,可以在特定的环境下指定类模板的特定实现,并且要求某些模板参数仍然由用户来定义。

复制代码
//局部特化:两个模板参数具有相同的类型
template <typename T>
class MyClass<T, T> {
    ...
};

//局部特化:两个模板参数的类型都是int
template <typename T>
class MyClass<T, int> {
    ...
};

//局部特化:两个模板参数都是指针类型
template <typename T1, typename T2> 
class MyClass<T1*, T2*> {
    ...
};
复制代码

如果有多个局部特化同等程度的匹配某个声明,那么就称该声明具有二义性:

MyClass<int, int> m; //错误:同等程度的匹配MyClass<T, T>和MyClass<T, int>
MyClass<int*, int*> m; //错误:同等程度的匹配MyClass<T, T>和MyClass<T*, T*>

为了解决上述的二义性,可以提供一个指向相同类型指针的特化:

template<typename T>
class MyClass<T*, T*> {
    ...
};

五、缺省模板实参

对于类模板,还可以为模板参数定义缺省值,这些值就被称为缺省模板实参,而且还可以应用之前的模板参数,例如在类stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<>作为它的缺省值:

复制代码
template <typename T, typename CONT = std::vector<T> >
class Stack {
private:
    CONT elems;

public:
    void push(T const&);
    void pop();
};

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

template <typename T, typename CONT>
void Stack<T, CONT>::pop() {
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::pop():empty stack");
    }

    elems.pop_back();
}
复制代码

从上述示例可以看到:类模板含有两个模板参数,因此每一个成员函数的定义都必须具有这两个参数,我们仍然可以像前面的示例一样使用这个stack,如果只传递第一个类型实参给这个类模板,那么将会利用vector来管理stack的元素。另外当在程序中声明stack对象的时候,还可以指定容器的类型:

复制代码
int main() {
    //int栈
    Stack<int> intStack;

    //double栈,它使用std::deque来管理元素
    Stack<double, std::deque<double> > dblStack;
    
    return 0;
}
复制代码

六、总结

(1).类模板是具有如下性质的类:在类的实现中,可以有一个或多个类型还没有被指定

(2).为了使用类模板,可以传入某个具体类型作为模板参数;然后编译器将会基于该类型来实例化类模板

(3).对于类模板而言,只有那些被调用的成员函数才会被实例化

(4).可以用某种特定类型特化类模板

(5).可以用某种特定类型局部特化类模板

(6).可以为类模板的实参定义缺省值,这些值还可以引用之前的模板参数

 

posted @   TechNomad  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示