You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stack<std::pair< int, int>> ps; // note: std::pair<> has no operator<<
defined
ps.push({4, 5}); // OK
ps.push({6, 7}); // OK
std::cout << ps.top().first << ’\n’; // OK
std::cout << ps.top().second << ’\n’; // OK
ps.printOn(std::cout); // ERROR: operator<< not supported for element type
关于类模板友元规则有很多,知道有哪几大类规则即可(Friend Classes of Class Templates、Friend Functions of Class Templates、Friend Templates),用到的时候再查也来得及。可以参考:《C++ Templates Second Edition》12.5小节。 (关注公众号:红宸笑。回复:电子书 获取pdf)
类模板的全特化
与函数模板类似,但是要注意的是,如果你想要全特化一个类模板,你必须全特化这个类模板的所有成员函数。
template <>
classStack<std::string> {
private:
std::deque<std::string> elems; // elementspublic:voidpush(std::string const &); // push elementvoidpop(); // pop element
std::string const &top() const; // return top elementboolempty() const { // return whether the stack is emptyreturn elems.empty();
}
};
void Stack<std::string>::push(std::string const &elem) {
elems.push_back(elem); // append copy of passed elem
}
void Stack<std::string>::pop() {
assert(!elems.empty());
elems.pop_back(); // remove last element
}
std::string const &Stack<std::string>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
// partial specialization of class Stack<> for pointers:template <typename T>
classStack<T *> {
private:
std::vector<T *> elems; // elementspublic:voidpush(T *); // push element
T *pop(); // pop element
T *top() const; // return top elementboolempty() const { // return whether the stack is emptyreturn elems.empty();
}
};
template <typename T>
void Stack<T *>::push(T *elem) {
elems.push_back(elem); // append copy of passed elem
}
template <typename T>
T *Stack<T *>::pop() {
assert(!elems.empty());
T *p = elems.back();
elems.pop_back(); // remove last elementreturn p; // and return it (unlike in the general case)
}
template <typename T>
T *Stack<T *>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
注意类声明与全特化的不同:
template<typename T>
classStack<T*> {
};
使用:
Stack<int*> ptrStack; // stack of pointers (special implementation)
ptrStack.push(newint{42});
类模板声明、实现与使用
声明:
实现:
使用:
有两点需要注意
Stack<T>
简写为Stack
,例如:但是在类外,还是需要
Stack<T>
:Class Instantiation
instantiation的概念在函数模板中说过。在类模板中,类模板函数只有在被调用时才会被
instantiate
。在上面的例子中,push()
和top()
都会被Stack<int>
和Stack<std::string>
所instantiate
,但是pop()
只被Stack<std::string>
所instantiate
。使用类模板的部分成员函数
我们为Stack新提供
printOn()
函数,这需要elem
支持<<
操作:根据上一小节关于类模板的
instantiation
,只有使用到该函数时才会进行该函数的instantiation
。假如我们的模板参数是元素不支持<<
的std::pair< int, int>
,那么仍然可以使用类模板的其他函数,只有调用printOn
的时候才会报错:Concept
这就引出了一个问题,我们如何知道一个类模板和它的模板函数需要哪些操作?
在c++11中,我们有
static_assert
:假如没有static_assert,提供的模板参数不满足
std::is_default_constructible
,代码也编译不过。但是编译器产出的错误信息会很长,包含整个模板instantiation
的信息——从开始instantiation
直到引发错误的地方,让人很难找出错误的真正原因。所以使用static_assert是一个办法。但是static_assert适用于做简单的判断,实际场景中我们的场景会更加复杂,例如判断模板参数是否具有某个特定的成员函数,或者要求它们支持互相比较,这种情况下使用concept就比较合适。
concept是c++20中用来表明模板库限制条件的一个特性,在后面会单独说明concept,这里为了文章篇幅先暂时只说一下为什么要有concept.
友元
首先需要明确一点:友元虽然看起来好像是该类的一个成员,但是友元不属于这个类。这里友元指的是友元函数和友元类。这点对于理解下面各种语法规则至关重要。
方式一
这里在类里声明的友元函数使用的是与类模板不同的模板参数
<template typename U>
,是因为友元函数的模板参数与类模板的模板参数不互相影响,这可以理解为我们创建了一个新的函数模板。再举一个友元类的例子:
这里也使用的是不同的模板参数。也就是:
bar<char>
、bar<int>
、bar<float>
和其他任何类型的bar都是foo<char>
的友元。方式二
这里提前声明了Stack与
operator<<
,并且在类模板中,operator<<
后面使用了<T>
,没有使用新的模板参数。与第一种方式对比,这里创建了一个特例化的非成员函数模板作为友元 (注意这个友元函数的声明,是没有<T>
的 )。方式一中第二个友元类的例子用本方式写是:
对比的,这里只有
bar<char>
是foo<char>
的友元类。关于类模板友元规则有很多,知道有哪几大类规则即可(Friend Classes of Class Templates、Friend Functions of Class Templates、Friend Templates),用到的时候再查也来得及。可以参考:《C++ Templates Second Edition》12.5小节。 (关注公众号:红宸笑。回复:电子书 获取pdf)
类模板的全特化
与函数模板类似,但是要注意的是,如果你想要全特化一个类模板,你必须全特化这个类模板的所有成员函数。
在类声明的开始处,需要使用
template<>
并且表明类模板的全特化参数类型:在成员函数中,需要将
T
替换成特化的参数类型:类模板的偏特化
类模板可以针对某一些特性场景进行部分特化,比如我们针对模板参数是指针进行偏特化:
注意类声明与全特化的不同:
使用:
多模板参数的偏特化
与函数模板重载类似,比较好理解。
原模板:
重载:
使用:
同样也会有重载冲突:
默认模板参数
也与函数默认参数类似。比如我们为
Stack<>
增加一个默认参数,代表管理Stack元素的容器类型:注意定义成员函数的模板参数变成了2个:
使用:
Type Aliases
new name for complete type
两种方式:typedef、using(c++11)
alias template
using比typedef有一个很大的优势是可以定义alias template:
再强调一下,不可以将类模板声明或定义在函数或者块作用域内。通常类模板只能定义在global/namespace 作用域,或者是其它类的声明里面。
在之前函数模板文章中介绍过的
std::common_type_t
,实际上就是一个别名:Alias Templates for Member Types
使用:
关键字typename
上面的注释说明了:
typename MyType<T>::iterator
里的typename
是必须的,因为这里的typename代表后面紧跟的是一个定义在类内的类型,否则,iterator
会被当成一个静态变量或者枚举:Using or Typedef
个人倾向使用using :
=
,更符合看代码的习惯、更清晰:alias template
更方便。类模板的参数推导 Class Template Argument Deduction
或许你会觉得每次使用模板时都需要显示的指明模板参数类型会多此一举,如果类模板能像
auto
一样自动推导模板类型就好了。在C++17中,这一想法变成了可能:如果构造函数能够推导出所有的模板参数,那么我们就不需要显示的指明模板参数类型。添加能推断出类模型类型的构造函数:
使用:
之所以添加
Stack () = default;
是为了Stack<int> s;
这种默认构造不报错。Deduction Guides
我们可以使用
Deduction Guides
来提供额外的模板参数推导规则,或者修正已有的模板参数推断规则。更多规则和用法可以看:Class template argument deduction (CTAD) (since C++17)
(完)
朋友们可以关注下我的公众号,获得最及时的更新:
The text was updated successfully, but these errors were encountered: