文章目录
- 定义模板
- 模板实参推断
-
- 理解std::move
- 转发
- 重载与模板
- 可变参数模板
- 模板特例化
- 定义函数模板特例化
定义模板
函数模板
template <typename T>
int compare (const T &V1, const T &v2)
{
if(v1<v2) return -1;
if(v2<v1) return 1;
return 0;
}
模板定义以关键字tempalte 开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用小于号和大于号包围起来
在模板定义中,模板参数列表不能为空。
实例化函数模板
模板类型参数
上述的compare 函数有一个模板类型参数。一般将类型参数看作类型说明符就像内置类型或类类型说明符一样。类型参数可与i用来指定返回类型或者函数的参数类型,以及函数体内用于标量声明或者类型转换。
template<typename T> T FOO(t* p)
{
T tmp=*p// tmp 的类型是指针p指向的类型
return tmp;
}
类型参数前必须使用关键字class或typename
非类型模板参数
在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数。
非类型模板参数的模板实参必须是常量表达式。
inline 和constexpr的函数模板
inline或constexpr说明符放在模板参数列表之后,返回类型之前:
template<tempename T>inline T min(const T&,const T&);//正确inline说明符跟在模板参数列表之后
编写类型无关的代码
- 模板中的函数参数是const的引用
- 函数体中条件判断仅使用<比较运算
如果我们真的关心类型无关和可移植性,可能需要使用less来定义函数
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;
}
模板程序应该尽量减少对实参类型的要求。
模板编译
当编译器遇到一个模板定义时,它并不生成代码,只有当我们实例化出模板的一个特定版本时,编译器才会生成艾玛。当我们使用而非定义模板时,编译器才生成代码。这一特性影响我们如何阻止代码以及错误和时被检测到。
通常,我们调用一个函数 时,编译器只需要掌握函数声明,类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,哦我们将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
模板则不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数定义。因此,与非模板代码不同,模板的头文件通常既包含声明也包含定义。
函数模板和类模板成员函数的定义通常放在头文件中。
例子
以下是C++ 中模板和头文件相关示例:
1. 模板定义(假设在 template_example.h
头文件中)
// 函数模板
template <typename T>
T add(T a, T b) {
return a + b;
}
// 类模板
template <typename T>
class MyClass {
public:
T value;
MyClass(T v) : value(v) {}
T getValue() {
return value;
}
};
这里定义了一个函数模板 add
用于对相同类型的两个值相加,还定义了一个类模板 MyClass
,它有一个成员变量 value
以及相关的成员函数 getValue
。
2. 模板使用(假设在 main.cpp
源文件中)
#include "template_example.h"
#include <iostream>
int main() {
// 使用函数模板
int result1 = add(3, 5);
std::cout << "Addition result (int): " << result1 << std::endl;
double result2 = add(3.5, 5.5);
std::cout << "Addition result (double): " << result2 << std::endl;
// 使用类模板
MyClass<int> myObj(10);
std::cout << "MyClass value: " << myObj.getValue() << std::endl;
return 0;
}
在 main.cpp
中,通过 #include "template_example.h"
包含了定义模板的头文件,这样就满足了模板使用时相关定义可见的要求。在 main
函数里,分别对函数模板 add
进行了 int
和 double
类型的实例化调用,还实例化了类模板 MyClass
为 MyClass<int>
并使用其成员函数。
在这个过程中,模板设计者(编写 template_example.h
的人)提供了模板定义;模板用户(编写 main.cpp
的人)包含了模板定义所在的头文件来满足模板实例化时名字可见的要求 。
大多数编译错误在实例化期间报告
类模板
编译器不能为类模板推断模板参数类型,为了使用类模板我们必须在模板名后的尖括号中提供额外信息-来代替模板参数的模板实参列表。
定义类模板
#include <memory>
#include <vector>
#include <initializer_list>
#include <stdexcept>
#include <string>
template <typename T>
class Blob {
public:
typedef T value_type;
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); }
// 移动版本,参见13.6.3节(第484页)
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// 元素访问
T& back();
T& operator[](size_type i); // 在14.5节(第501页)中定义
private:
std::shared_ptr<std::vector<T>> data;
// 若data[i]无效,则抛出msg
void check(size_type i, const std::string &msg) const;
};
// 构造函数实现
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)) {}
// pop_back函数实现
template <typename T>
void Blob<T>::pop_back() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
// back函数实现
template <typename T>
T& Blob<T>::back() {
check(0, "back on empty Blob");
return data->back();
}
// operator[]函数实现
template <typename T>
T& Blob<T>::operator[](size_type i) {
check(i, "subscript out of range");
return (*data)[i];
}
// check函数实现
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);
}
}
int main() {
Blob<int> intBlob = {1, 2, 3};
std::cout << "Blob size: " << intBlob.size() << std::endl;
intBlob.push_back(4);
std::cout << "After push_back, Blob back element: " << intBlob.back() << std::endl;
intBlob.pop_back();
std::cout << "After pop_back, Blob size: " << intBlob.size() << std::endl;
try {
intBlob[10]; // 故意触发越界异常
} catch (const std::out_of_range& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
在模板作用域中引用模板类型
类模板成员函数
在类代码内简化模板类名的使用
在类模板外使用类模板名
类模板和友元
#include <iostream>
// 模板友元类
template <typename U>
class TemplateFriend {
public:
void accessBlob(Blob<U>& b);
};
// 类模板
template <typename T>
class Blob {
template <typename U>
friend class TemplateFriend; // 模板友元声明,授权给所有友元模板实例
private:
T data;
public:
Blob(T t) : data(t) {}
T getData() const { return data; }
};
// 模板友元类中访问Blob类模板实例的成员函数定义
template <typename U>
void TemplateFriend<U>::accessBlob(Blob<U>& b) {
std::cout << "Accessed data in Blob<" << typeid(U).name() << ">: " << b.getData() << std::endl;
}
#include <iostream>
#include <vector>
#include <memory>
#include <stdexcept>
// 前置声明
template <typename> class BlobPtr;
template <typename> class Blob;
// 前置声明运算符重载
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> bool operator!=(const Blob<T>&, const Blob<T>&);
// Blob类模板定义
template <typename T>
class Blob {
friend class BlobPtr<T>;
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
public:
using value_type = T;
using size_type = typename std::vector<T>::size_type;
// 构造函数
Blob() : data(std::make_shared<std::vector<T>>()) {}
Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) {}
// 容量操作
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() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
// 元素访问
T& front() {
check(0, "front on empty Blob");
return data->front();
}
const T& front() const {
check(0, "front on empty Blob");
return data->front();
}
T& back() {
check(0, "back on empty Blob");
return data->back();
}
const T& back() const {
check(0, "back on empty Blob");
return data->back();
}
T& operator[](size_type i) {
check(i, "subscript out of range");
return (*data)[i];
}
const T& operator[](size_type i) const {
check(i, "subscript out of range");
return (*data)[i];
}
private:
std::shared_ptr<std::vector<T>> data;
void check(size_type i, const std::string& msg) const {
if (i >= data->size())
throw std::out_of_range(msg);
}
};
// BlobPtr类模板定义 - 修正自增/自减逻辑
template <typename T>
class BlobPtr {
public:
BlobPtr() : curr(0) {}
BlobPtr(Blob<T>& a, size_t sz = 0) : wptr(a.data), curr(sz) {}
// 解引用运算符
T& operator*() const {
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
T* operator->() const {
return &this->operator*();
}
// 前置自增运算符 - 修正:先检查后增加(确保新位置有效)
BlobPtr& operator++() {
check(curr, "increment past end of BlobPtr"); // 检查当前位置是否合法
++curr; // 移动到下一个位置
if (curr != wptr.lock()->size()) {
check(curr, "increment past end of BlobPtr"); // 确保新位置有效
}
return *this;
}
// 后置自增运算符
BlobPtr operator++(int) {
BlobPtr ret = *this;
++*this; // 复用前置++
return ret;
}
// 前置自减运算符 - 修正:先检查后减少(确保新位置有效)
BlobPtr& operator--() {
if (curr == 0) {
throw std::out_of_range("decrement past begin of BlobPtr");
}
--curr; // 移动到前一个位置
check(curr, "decrement past begin of BlobPtr"); // 确保新位置有效
return *this;
}
// 后置自减运算符
BlobPtr operator--(int) {
BlobPtr ret = *this;
--*this; // 复用前置--
return ret;
}
private:
std::weak_ptr<std::vector<T>> wptr;
size_t curr;
std::shared_ptr<std::vector<T>> check(size_t i, const std::string& msg) const {
auto ret = wptr.lock();
if (!ret)
throw std::runtime_error("unbound BlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret;
}
};
// 相等运算符 - 比较内容而非指针
template <typename T>
bool operator==(const Blob<T>& a, const Blob<T>& b) {
if (a.data == b.data) return true; // 同一对象
if (a.size() != b.size()) return false; // 大小不同
// 逐元素比较
for (size_t i = 0; i < a.size(); ++i) {
if (a[i] != b[i]) return false;
}
return true;
}
// 不等运算符
template <typename T>
bool operator!=(const Blob<T>& a, const Blob<T>& b) {
return !(a == b);
}
// 测试函数
int main() {
try {
// 创建Blob并添加元素
Blob<int> blob = {1, 2, 3, 4};
// 使用BlobPtr遍历元素
BlobPtr<int> ptr(blob);
std::cout << "遍历元素: ";
for (size_t i = 0; i < blob.size(); ++i) {
std::cout << *ptr << " ";
++ptr; // 测试前置自增
}
std::cout << std::endl;
// 测试后置自增
BlobPtr<int> ptr2(blob);
std::cout << "后置自增测试: " << *ptr2++ << " " << *ptr2 << std::endl;
// 测试自减
--ptr; // 回到最后一个元素
std::cout << "自减测试: " << *ptr << std::endl;
// 测试相等运算符
Blob<int> blob2 = {1, 2, 3, 4};
Blob<int> blob3 = {1, 2, 3};
std::cout << "相等测试: " << (blob == blob2) << " " << (blob == blob3) << std::endl;
// 测试异常处理
BlobPtr<int> emptyPtr;
// *emptyPtr; // 会抛出异常:unbound BlobPtr
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}
return 0;
}
/*
遍历元素: 1 2 3 4
后置自增测试: 1 2
自减测试: 4
相等测试: 1 0
*/
通用和特定的模板友好关系
模板类型别名
类模板的static成员
// templateStatic.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <iostream>
#include <string>
// 模板类定义
template <typename T>
class MyTemplateClass {
public:
// 静态成员函数声明
static void staticFunction();
// 静态数据成员声明
static T staticData;
// 普通成员函数,用于展示静态数据成员的使用
void printStaticData() {
std::cout << "Static data value: " << staticData << std::endl;
}
};
// 模板类静态数据成员的定义,需要指定模板参数
template <typename T>
T MyTemplateClass<T>::staticData = T();
// 模板类静态成员函数的定义,需要指定模板参数
template <typename T>
void MyTemplateClass<T>::staticFunction() {
std::cout << "This is a static function of MyTemplateClass with type " << typeid(T).name() << std::endl;
}
int main() {
// 使用int类型实例化模板类
MyTemplateClass<int> intInstance;
MyTemplateClass<int>::staticFunction(); // 通过类名调用静态成员函数
intInstance.printStaticData(); // 通过对象调用普通成员函数来打印静态数据成员
MyTemplateClass<int>::staticData = 42; // 修改int实例化的静态数据成员
intInstance.printStaticData(); // 再次打印验证修改
// 使用std::string类型实例化模板类
MyTemplateClass<std::string> stringInstance;
MyTemplateClass<std::string>::staticFunction(); // 通过类名调用静态成员函数
stringInstance.printStaticData(); // 通过对象调用普通成员函数来打印静态数据成员
MyTemplateClass<std::string>::staticData = "Hello, template!"; // 修改string实例化的静态数据成员
stringInstance.printStaticData(); // 再次打印验证修改
return 0;
}
模板参数
一个模板参数的名字也没有什么内在含义
模板参数与作用域
模板声明
// templateStatic.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <iostream>
#include <string>
// 模板类定义
template <typename T>
class MyTemplateClass {
public:
// 静态成员函数声明
static void staticFunction();
// 静态数据成员声明
static T staticData;
// 普通成员函数,用于展示静态数据成员的使用
void printStaticData() {
std::cout << "Static data value: " << staticData << std::endl;
}
};
// 模板类静态数据成员的定义,需要指定模板参数
template <typename T>
T MyTemplateClass<T>::staticData = T();
// 模板类静态成员函数的定义,需要指定模板参数
template <typename T>
void MyTemplateClass<T>::staticFunction() {
std::cout << "This is a static function of MyTemplateClass with type " << typeid(T).name() << std::endl;
}
int main() {
// 使用int类型实例化模板类
MyTemplateClass<int> intInstance;
MyTemplateClass<int>::staticFunction(); // 通过类名调用静态成员函数
intInstance.printStaticData(); // 通过对象调用普通成员函数来打印静态数据成员
MyTemplateClass<int>::staticData = 42; // 修改int实例化的静态数据成员
intInstance.printStaticData(); // 再次打印验证修改
// 使用std::string类型实例化模板类
MyTemplateClass<std::string> stringInstance;
MyTemplateClass<std::string>::staticFunction(); // 通过类名调用静态成员函数
stringInstance.printStaticData(); // 通过对象调用普通成员函数来打印静态数据成员
MyTemplateClass<std::string>::staticData = "Hello, template!"; // 修改string实例化的静态数据成员
stringInstance.printStaticData(); // 再次打印验证修改
return 0;
}
默认模板实参
// Template_Compare.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <functional>
#include <string>
// compare有一个默认模板实参less<T>和一个默认函数实参F()
template <typename T, typename F = std::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;
}
int main() {
// 测试整数比较,使用默认比较准则(std::less<int>)
int num1 = 5;
int num2 = 10;
int result1 = compare(num1, num2);
if (result1 == -1) {
std::cout << num1 << " 小于 " << num2 << std::endl;
}
else if (result1 == 1) {
std::cout << num1 << " 大于 " << num2 << std::endl;
}
else {
std::cout << num1 << " 等于 " << num2 << std::endl;
}
// 测试字符串比较,使用默认比较准则(std::less<std::string>)
std::string str1 = "apple";
std::string str2 = "banana";
int result2 = compare(str1, str2);
if (result2 == -1) {
std::cout << str1 << " 小于 " << str2 << std::endl;
}
else if (result2 == 1) {
std::cout << str1 << " 大于 " << str2 << std::endl;
}
else {
std::cout << str1 << " 等于 " << str2 << std::endl;
}
// 自定义比较准则,用于比较整数绝对值大小
auto abs_compare = [](int a, int b) {
return std::abs(a) < std::abs(b);
};
int num3 = -8;
int num4 = 6;
int result3 = compare(num3, num4, abs_compare);
if (result3 == -1) {
std::cout << "| " << num3 << " | 小于 | " << num4 << " |" << std::endl;
}
else if (result3 == 1) {
std::cout << "| " << num3 << " | 大于 | " << num4 << " |" << std::endl;
}
else {
std::cout << "| " << num3 << " | 等于 | " << num4 << " |" << std::endl;
}
return 0;
}
模板默认参数与类模板
无论何时使用一个类模板。我们都必须在模板名之后街上尖括号,尖括号指出类必须从一个模板实例化而来。
#include <iostream>
// 模板类定义
template <class T = int> class Numbers {
public:
// 构造函数
Numbers(T v = 0) : val(v) {}
// 获取存储的值
T getValue() const {
return val;
}
// 设置存储的值
void setValue(T newVal) {
val = newVal;
}
private:
T val;
};
int main() {
// 显式指定类型实例化
Numbers<long double> lots_of_precision(3.14159265358979323846);
std::cout << "lots_of_precision的值: " << lots_of_precision.getValue() << std::endl;
// 使用默认类型实例化
Numbers<> average_precision;
average_precision.setValue(10);
std::cout << "average_precision的值: " << average_precision.getValue() << std::endl;
return 0;
}
成员模板
一个类(无论是普通类还是类模板),可以包含本身是模板的成员函数。这种成员被称为成员模板。
#include <iostream>
#include <memory>
// 函数对象类,对给定指针执行delete
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr) : os(s) {}
// 与任何函数模板相同,T的类型由编译器推断
template <typename T> void operator()(T *p) const {
os << "deleting unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os;
};
int main() {
// 示例1:使用默认的std::cerr输出调试信息
int* ptr1 = new int(42);
DebugDelete debugDelete1;
debugDelete1(ptr1);
// 示例2:指定输出流为std::cout
double* ptr2 = new double(3.14);
DebugDelete debugDelete2(std::cout);
debugDelete2(ptr2);
// 示例3:与std::unique_ptr结合使用
std::unique_ptr<int, DebugDelete> ptr3(new int(10), DebugDelete());
// 当ptr3超出作用域时,DebugDelete的operator()会被调用
// 会打印调试信息并释放内存
return 0;
}
模板自己的类型参数成为友元
template class Bar:定义了一个类模板 Bar,Type 是模板参数,后续可代入 int、std::string 等具体类型实例化类。
friend Type;:这是核心逻辑。当用某一具体类型(比如 int)实例化 Bar 时(如 Bar barObj; ),Type 就被替换为 int,代码等价于 friend int; ,意味着 int 类型(及其对应对象、操作)能访问 Bar 类的私有 / 保护成员 。
template <typename Type>
class Bar {
friend Type;
private:
Type data;
public:
Bar(Type val) : data(val) {}
};
int main() {
Bar<int> bar(10);
// 因 friend int; ,int 类型相关操作(这里直接用 int 变量访问)可碰私有成员
int x = bar.data; // 合法,不会编译报错
return 0;
}
模板类型别名
模板类型别名(Template Type Alias)是 C++11 引入的一项特性,允许为模板类或模板函数定义简化的别名,从而提高代码可读性和可维护性。下面通过具体示例详细说明:
1. 基本语法:使用 using
定义模板别名
通过 template<typename... Args> using 别名 = 原模板<Args...>
的形式定义。
示例 1:为 std::pair
定义模板别名
#include <utility>
// 定义模板别名 twin,固定两个类型参数相同
template<typename T>
using twin = std::pair<T, T>;
// 使用别名创建对象
twin<int> coords(10, 20); // 等价于 std::pair<int, int>
twin<std::string> names("Alice", "Bob"); // 等价于 std::pair<std::string, std::string>
示例 2:为函数模板定义别名
#include <vector>
#include <functional>
// 定义接受 vector<T> 并返回 T 的函数类型别名
template<typename T>
using VectorProcessor = std::function<T(const std::vector<T>&)>;
// 使用别名声明函数变量
VectorProcessor<int> sum = [](const std::vector<int>& v) {
int total = 0;
for (int x : v) total += x;
return total;
};
2. 为什么需要模板别名?
简化复杂模板:当模板参数较多或嵌套层级深时,别名能显著减少代码冗余。
// 原始写法(复杂) std::unordered_map<std::string, std::vector<std::pair<int, double>>> data; // 使用别名(简洁) template<typename K, typename V> using MapVectorPair = std::unordered_map<K, std::vector<std::pair<int, V>>>; MapVectorPair<std::string, double> data; // 清晰易读
实现模板特化:通过别名隐藏底层实现,方便统一修改。
// 根据平台选择不同的容器实现 #ifdef _WIN32 template<typename T> using PlatformVector = std::vector<T>; // Windows 使用 std::vector #else template<typename T> using PlatformVector = std::deque<T>; // Linux 使用 std::deque #endif // 用户代码无需关心底层实现 PlatformVector<int> numbers;
3. 与 typedef
的对比
typedef
无法直接用于模板,而using
可以。// 错误:typedef 不能直接定义模板别名 typedef std::vector<T> Vec; // 无法编译 // 正确:使用 using 定义模板别名 template<typename T> using Vec = std::vector<T>;
using
语法更直观,尤其对于函数指针类型。// 使用 typedef 定义函数指针类型(复杂) typedef void (*Callback)(int, double); // 使用 using 定义(简洁) using Callback = void(*)(int, double);
4. 进阶应用:模板别名与模板参数
固定部分模板参数:通过别名绑定部分参数,生成新模板。
#include <memory> // 固定 allocator 为 std::allocator 的 vector 别名 template<typename T> using MyVector = std::vector<T, std::allocator<T>>; // 使用别名创建 vector MyVector<int> vec; // 等价于 std::vector<int, std::allocator<int>>
结合模板元编程:在编译期进行类型计算。
// 条件选择类型的别名模板 template<bool Condition, typename T, typename U> using ConditionalType = typename std::conditional<Condition, T, U>::type; // 根据条件选择不同类型 ConditionalType<(sizeof(int) > 4), long, int> num; // 在 64 位系统上等价于 long
5. 注意事项
别名不是新类型:模板别名只是语法糖,不会创建新的类型。
using IntVector = std::vector<int>; static_assert(std::is_same_v<IntVector, std::vector<int>>, "Same type"); // 通过编译
别名模板不能偏特化:但可以通过原模板的特化间接实现。
template<typename T> using Container = std::vector<T>; // 错误:不能直接特化别名模板 // template<> using Container<bool> = std::vector<bool>; // 正确:特化原模板 template<> struct std::vector<bool> { /* 特化实现 */ };
总结
模板类型别名是 C++ 中强大的抽象工具,能够:
- 简化复杂模板的使用,提高代码可读性。
- 实现平台无关的代码抽象。
- 结合模板元编程实现编译期类型计算。
合理使用模板别名可以让代码更加简洁、灵活,同时保持类型系统的清晰性。
类模板的static成员
模板默认实参与类模板
template <class T=int>
class Numbers
{
public:
Number< T v=0):val(v)
{
}
private:
T val;
};
Number<long double> losts_pre;
Number<> precision;
成员模板
一个类可以包含本身是模板的成员函数,这种成员被称为成员模板,成员模板不能是虚函数。
普通类的成员模板-删除器
#include <iostream>
#include <memory>
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr) : os(s) {}
template<typename T>
void operator()(T *p) const {
os << "delete unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os;
};
class MyClass {
public:
MyClass(int val) : value(val) {
std::cout << "MyClass constructed with value: " << value << std::endl;
}
~MyClass() {
std::cout << "MyClass destructed with value: " << value << std::endl;
}
int getValue() const { return value; }
private:
int value;
};
int main() {
// 使用 DebugDelete 作为 unique_ptr 的删除器
{
std::unique_ptr<MyClass, DebugDelete> ptr(
new MyClass(42),
DebugDelete(std::cout)
);
std::cout << "ptr value: " << ptr->getValue() << std::endl;
// ptr 离开作用域时,DebugDelete 会被调用
}
// 使用默认的 cerr 流
{
std::unique_ptr<int, DebugDelete> intPtr(
new int(100),
DebugDelete()
);
std::cout << "intPtr value: " << *intPtr << std::endl;
}
// 用于原始指针的手动删除
double *rawPtr = new double(3.14);
DebugDelete()(rawPtr); // 直接调用删除器
double *p=new double;
DebugDelete p;
d(p);
return 0;
}
类模板的成员模板
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <initializer_list>
// Blob类模板定义
template<typename T>
class Blob {
public:
using value_type = T;
using size_type = typename std::vector<T>::size_type;
// 默认构造函数
Blob() : data(std::make_shared<std::vector<T>>()) {}
// 从初始化列表构造
Blob(std::initializer_list<T> il) :
data(std::make_shared<std::vector<T>>(il)) {}
// 从迭代器范围构造(关键实现)
template <typename It>
Blob(It b, It e) :
data(std::make_shared<std::vector<T>>(b, e)) {}
// 元素访问
T& front() { return data->front(); }
const T& front() const { return data->front(); }
T& back() { return data->back(); }
const T& back() const { return data->back(); }
T& operator[](size_type i) { return (*data)[i]; }
const T& operator[](size_type i) const { return (*data)[i]; }
// 容量操作
bool empty() const { return data->empty(); }
size_type size() const { return data->size(); }
private:
std::shared_ptr<std::vector<T>> data;
};
// 辅助函数:简化Blob创建
template<typename T>
Blob<T> make_blob(std::initializer_list<T> il) {
return Blob<T>(il);
}
int main() {
// 1. 使用初始化列表构造
Blob<int> numbers = {1, 2, 3, 4, 5};
std::cout << "numbers size: " << numbers.size() << std::endl;
std::cout << "numbers[2]: " << numbers[2] << std::endl;
// 2. 使用迭代器范围构造(从vector)
std::vector<std::string> words = {"hello", "world", "!"};
Blob<std::string> text(words.begin(), words.end());
std::cout << "text size: " << text.size() << std::endl;
std::cout << "text.back(): " << text.back() << std::endl;
// 3. 使用迭代器范围构造(从数组)
double arr[] = {1.1, 2.2, 3.3};
Blob<double> doubles(arr, arr + 3);
std::cout << "doubles[1]: " << doubles[1] << std::endl;
// 4. 使用辅助函数构造
auto chars = make_blob({'a', 'b', 'c'});
std::cout << "chars.front(): " << chars.front() << std::endl;
return 0;
}
控制实例化
对每个实例化声明,在程序中某个位置必须有其显式的实例化定义。
实例化定义会实例化所有成员
// ====================
// blob.h - Blob类模板定义
// ====================
#pragma once
#include <memory>
#include <vector>
#include <string>
template<typename T>
class Blob {
public:
using value_type = T;
using size_type = typename std::vector<T>::size_type;
Blob();
Blob(std::initializer_list<T> il);
T& front();
const T& front() const;
T& back();
const T& back() const;
T& operator[](size_type i);
const T& operator[](size_type i) const;
bool empty() const;
size_type size() const;
private:
std::shared_ptr<std::vector<T>> data;
};
// 函数模板声明
template<typename T>
int compare(const T&, const T&);// ====================
// blob.cpp - 模板实现文件
// ====================
#include "blob.h"
#include <stdexcept>
// Blob类模板的实现
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>
T& Blob<T>::front() {
if (data->empty()) throw std::out_of_range("front on empty Blob");
return data->front();
}
template<typename T>
const T& Blob<T>::front() const {
if (data->empty()) throw std::out_of_range("front on empty Blob");
return data->front();
}
template<typename T>
T& Blob<T>::back() {
if (data->empty()) throw std::out_of_range("back on empty Blob");
return data->back();
}
template<typename T>
const T& Blob<T>::back() const {
if (data->empty()) throw std::out_of_range("back on empty Blob");
return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i) {
return (*data)[i];
}
template<typename T>
const T& Blob<T>::operator[](size_type i) const {
return (*data)[i];
}
template<typename T>
bool Blob<T>::empty() const {
return data->empty();
}
template<typename T>
size_type Blob<T>::size() const {
return data->size();
}
// 显式实例化声明(告诉编译器其他地方会有实例化定义)
extern template class Blob<std::string>;
extern template int compare(const int&, const int&);
// 显式实例化定义(只在这个文件中生成实例化代码)
template class Blob<std::string>;
template int compare(const int&, const int&);
// 函数模板实现
template<typename T>
int compare(const T& v1, const T& v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}// ====================
// main.cpp - 主程序文件
// ====================
#include "blob.h"
#include <iostream>
// 声明外部已实例化的模板
extern template class Blob<std::string>;
extern template int compare(const int&, const int&);
int main() {
// 使用预实例化的 Blob<std::string>
Blob<std::string> words = {"hello", "world"};
std::cout << "First word: " << words.front() << std::endl;
// 使用预实例化的 compare<int>
int a = 10, b = 20;
std::cout << "Compare result: " << compare(a, b) << std::endl;
// 其他类型仍会按需实例化
Blob<double> doubles = {1.1, 2.2};
std::cout << "Double size: " << doubles.size() << std::endl;
return 0;
}
效率与灵活性
在编译绑定删除器
特点:删除器类型在编译期确定,无运行时开销。
实现:通过模板参数或函数重载实现
#include <iostream>
#include <memory>
// 编译时删除器示例:文件句柄管理
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
std::fclose(fp);
std::cout << "File closed by FileDeleter" << std::endl;
}
}
};
// 编译时删除器示例:网络连接管理
struct ConnectionDeleter {
void operator()(void* conn) const {
if (conn) {
// 释放网络连接的逻辑
std::cout << "Connection closed by ConnectionDeleter" << std::endl;
}
}
};
// 使用编译时删除器的智能指针
void compile_time_example() {
// 文件管理
std::unique_ptr<FILE, FileDeleter> file(
std::fopen("test.txt", "r"),
FileDeleter{}
);
// 网络连接管理
std::unique_ptr<void, ConnectionDeleter> conn(
/* 获取连接的函数 */ nullptr,
ConnectionDeleter{}
);
}
运行时候绑定删除器
特点:删除器在运行时确定,通过函数指针或多态实现。
实现:使用 std::function 或基类指针。
#include <iostream>
#include <memory>
#include <functional>
// 运行时删除器示例:通用资源管理
using Deleter = std::function<void(void*)>;
struct Resource {
int id;
Resource(int i) : id(i) { std::cout << "Resource " << id << " created" << std::endl; }
~Resource() { std::cout << "Resource " << id << " destroyed" << std::endl; }
};
// 不同的删除策略
void database_deleter(void* ptr) {
if (ptr) {
std::cout << "Releasing database connection..." << std::endl;
delete static_cast<Resource*>(ptr);
}
}
void file_deleter(void* ptr) {
if (ptr) {
std::cout << "Closing file handle..." << std::endl;
delete static_cast<Resource*>(ptr);
}
}
// 使用运行时删除器的智能指针
void runtime_example() {
// 模拟运行时决策
bool is_database = true;
Deleter deleter = is_database ? database_deleter : file_deleter;
// 使用 std::shared_ptr 存储任意删除器
std::shared_ptr<Resource> res(
new Resource(42),
[deleter](Resource* ptr) { deleter(ptr); }
);
}
多态删除器(运行时候绑定的另外一种实现)
#include <iostream>
#include <memory>
// 抽象删除器基类
struct BaseDeleter {
virtual void operator()(void* ptr) const = 0;
virtual ~BaseDeleter() = default;
};
// 具体删除器实现
struct DatabaseDeleter : public BaseDeleter {
void operator()(void* ptr) const override {
if (ptr) {
std::cout << "Database resource released" << std::endl;
delete static_cast<Resource*>(ptr);
}
}
};
struct FileDeleter : public BaseDeleter {
void operator()(void* ptr) const override {
if (ptr) {
std::cout << "File resource closed" << std::endl;
delete static_cast<Resource*>(ptr);
}
}
};
// 使用多态删除器
void polymorphic_example() {
bool is_file = false;
std::unique_ptr<BaseDeleter> deleter =
is_file ?
std::make_unique<FileDeleter>() :
std::make_unique<DatabaseDeleter>();
// 自定义删除器的 lambda,调用多态删除器
auto custom_deleter = [deleter = std::move(deleter)](Resource* ptr) {
(*deleter)(ptr);
};
std::unique_ptr<Resource, decltype(custom_deleter)> res(
new Resource(99),
std::move(custom_deleter)
);
}
模板实参推断
对于函数模板,编译器利用调用中的函数实参来确定其模板参数,从函数实参中确定模板实参的过程被称为模板实参推断。
类型转换与模板类型参数
顶层const无论在形参中还是在实参中,都会被忽略
#include <iostream>
#include <string>
using namespace std;
// 函数模板:接受通用类型参数
template<typename T>
void printType(T value) {
cout << "类型: " << typeid(value).name() << ", 值: " << value << endl;
}
// 函数模板:接受引用参数
template<typename T>
void printRef(const T& ref) {
cout << "引用类型: " << typeid(ref).name() << ", 值: " << ref << endl;
}
// 函数模板:接受指针参数
template<typename T>
void printPtr(T* ptr) {
if (ptr) {
cout << "指针类型: " << typeid(*ptr).name() << ", 值: " << *ptr << endl;
} else {
cout << "空指针" << endl;
}
}
// 函数模板:接受数组参数
template<typename T, size_t N>
void printArray(T (&arr)[N]) {
cout << "数组大小: " << N << ", 元素: ";
for (size_t i = 0; i < N; ++i) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
// 1. 顶层const处理:忽略顶层const
const int x = 42;
printType(x); // T推导为int,顶层const被忽略
// 2. const转换:非const对象传给const引用
string message = "Hello";
printRef(message); // T推导为string,message被隐式转换为const引用
// 3. 数组到指针的转换:非引用形参
int numbers[5] = {1, 2, 3, 4, 5};
printType(numbers); // T推导为int*,数组转换为指针
// 4. 引用传递数组:保持数组类型
printArray(numbers); // T推导为int,N推导为5,保持数组特性
// 5. 函数指针转换
int add(int a, int b) { return a + b; }
printType(add); // T推导为int (*)(int, int),函数转换为函数指针
// 6. 不允许的转换示例
double pi = 3.14;
// printPtr(&pi); // 错误!double*不能隐式转换为int*
// printArray(pi); // 错误!double不是数组类型
return 0;
}
使用相同模版参数类型的函数参数
正确类型转换应用于普通函数实参
函数模版显示参数
某些情况下编译器无法推断出模版实参类型,其他一些情况下,我们希望允许用户控制模版实例化,
当函数返回类型与参数列表中任何类型都不相同时,这两种情况最长出现。
指定显示模版参数
正确类型转换应用于显示指定的实参
尾置返回类型与类型转换
以下是对该函数模板完整例程的详细说明,包含代码补充、功能解析和调用示例:
完整代码示例
#include <iostream>
#include <vector>
#include <string>
// 提供 decltype 等特性支持
using namespace std;
// 尾置返回函数模板
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
// 这里可添加对序列的处理逻辑,比如遍历、修改(若允许)等
// 示例:简单遍历打印序列元素(仅演示,实际可按需扩展)
for (It it = beg; it != end; ++it) {
cout << *it << " ";
}
cout << endl;
return *beg; // 返回序列首元素的引用
}
int main() {
// 测试 int 类型的 vector
vector<int> vecInt = {10, 20, 30, 40};
// 调用函数模板,返回 int& 类型
int& resultInt = fcn(vecInt.begin(), vecInt.end());
cout << "返回的 int 引用值:" << resultInt << endl;
// 修改返回的引用,会影响原容器元素
resultInt = 100;
cout << "修改后 vecInt 首元素:" << vecInt[0] << endl;
// 测试 string 类型的数组
string arrStr[] = {"Hello", "World", "C++"};
// 调用函数模板,返回 string& 类型
string& resultStr = fcn(begin(arrStr), end(arrStr));
cout << "返回的 string 引用值:" << resultStr << endl;
// 修改返回的引用,会影响原数组元素
resultStr = "Hi";
cout << "修改后 arrStr 首元素:" << arrStr[0] << endl;
return 0;
}
代码详细说明
- 模板定义与尾置返回:
template <typename It>
:定义函数模板,It
是模板参数,可接收各种容器(如vector
、数组等 )的迭代器类型。auto fcn(It beg, It end) -> decltype(*beg)
:采用尾置返回语法,通过decltype(*beg)
推导返回类型。*beg
是解引用迭代器beg
,所以返回类型是迭代器指向元素的引用(比如vector<int>
的迭代器解引用后是int&
,返回类型就是int&
)。
- 函数逻辑:
- 函数内先演示了简单遍历序列(通过迭代器
beg
到end
遍历 )并打印元素,实际场景中可在此处编写更复杂的序列处理逻辑(如过滤、变换元素等 )。 return *beg;
:返回序列首元素的引用,意味着对返回值的修改会直接作用到原序列的首元素上。
- 函数内先演示了简单遍历序列(通过迭代器
- 主函数调用测试:
vector<int>
测试:创建vector<int>
容器,调用fcn
时传入其迭代器begin
和end
。得到返回的int&
引用后,修改该引用会同步改变原vector
的首元素值。string
数组测试:创建string
数组,利用begin
和end
(C++11 及以上对数组的支持 )传入迭代器,调用fcn
得到string&
引用,修改引用也会改变原数组的首元素。
运行结果示例
假设编译运行上述代码,输出大致如下:
10 20 30 40
返回的 int 引用值:10
修改后 vecInt 首元素:100
Hello World C++
返回的 string 引用值:Hello
修改后 arrStr 首元素:Hi
体现了函数模板通过尾置返回适配不同容器迭代器、返回元素引用并能修改原序列的特性。
进行类型转换的标准库模版类
类型推导核心链条:
decltype(*beg):先通过 decltype 获取迭代器解引用后的类型。比如 vector::iterator 解引用后是 int&,数组 string[] 迭代器解引用后是 string&。
remove_reference<…>::type:用 remove_reference 去除引用。对于 int& 会得到 int,对于 string& 会得到 string,这样就把 “引用类型” 转成了 “值类型”。
typename:因为 remove_reference<decltype(*beg)> 依赖模板参数 It,属于 “依赖型名称”,所以必须用 typename 告诉编译器 ::type 是一个类型(而非静态成员变量等 )。
返回值特性:
函数返回的是序列首元素的值拷贝(而非引用 )。因此,在 main 中修改 resultInt、resultStr 时,不会影响原容器 / 数组的元素,保证了原数据的独立性。
模板适配性:
无论迭代器指向的是 vector、普通数组,还是其他支持迭代器的容器(如 list、deque 等 ),只要迭代器解引用类型可被 remove_reference 处理,就能正确推导返回类型,实现泛型设计。
#include <iostream>
#include <vector>
#include <type_traits> // 包含 remove_reference 等类型 traits
#include <string>
using namespace std;
// 函数模板:返回序列首元素的值拷贝
template <typename It>
auto fcn2(It beg, It end)
-> typename remove_reference<decltype(*beg)>::type
{
// 模拟处理序列:这里简单遍历打印元素(可扩展复杂逻辑)
for (It it = beg; it != end; ++it) {
cout << *it << " ";
}
cout << endl;
return *beg; // 返回首元素的值拷贝
}
int main() {
// 测试 int 类型的 vector(元素是 int,*beg 是 int&)
vector<int> vecInt = {10, 20, 30};
// 调用后,返回值是 int(值拷贝)
int resultInt = fcn2(vecInt.begin(), vecInt.end());
cout << "返回的 int 拷贝值:" << resultInt << endl;
// 修改返回值,不影响原容器(因为是拷贝)
resultInt = 100;
cout << "修改后 vecInt 首元素:" << vecInt[0] << endl;
// 测试 string 类型的数组(元素是 string,*beg 是 string&)
string arrStr[] = {"Apple", "Banana"};
// 调用后,返回值是 string(值拷贝)
string resultStr = fcn2(begin(arrStr), end(arrStr));
cout << "返回的 string 拷贝值:" << resultStr << endl;
// 修改返回值,不影响原数组(因为是拷贝)
resultStr = "Orange";
cout << "修改后 arrStr 首元素:" << arrStr[0] << endl;
return 0;
}
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>
// 辅助函数:打印类型信息
template<typename T>
void print_type_info(const std::string& prefix) {
std::cout << prefix << " -> "
<< "is_reference: " << std::is_reference<T>::value << ", "
<< "is_pointer: " << std::is_pointer<T>::value << ", "
<< "is_const: " << std::is_const<T>::value << ", "
<< "is_signed: " << std::is_signed<T>::value << ", "
<< "is_lvalue_reference: " << std::is_lvalue_reference<T>::value << ", "
<< "is_rvalue_reference: " << std::is_rvalue_reference<T>::value << std::endl;
}
int main() {
std::cout << "=== 原始类型定义 ===" << std::endl;
using Int = int;
using IntRef = int&;
using IntConstRef = const int&;
using IntPtr = int*;
using IntConstPtr = const int*;
using UInt = unsigned int;
using Float = float;
print_type_info<Int>("int");
print_type_info<IntRef>("int&");
print_type_info<IntConstRef>("const int&");
print_type_info<IntPtr>("int*");
print_type_info<IntConstPtr>("const int*");
print_type_info<UInt>("unsigned int");
print_type_info<Float>("float");
std::cout << "\n=== 1. remove_reference ===" << std::endl;
print_type_info<std::remove_reference<IntRef>::type>("remove_reference<int&>");
print_type_info<std::remove_reference<IntConstRef>::type>("remove_reference<const int&>");
print_type_info<std::remove_reference<Int>::type>("remove_reference<int>");
std::cout << "\n=== 2. add_const ===" << std::endl;
print_type_info<std::add_const<Int>::type>("add_const<int>");
print_type_info<std::add_const<IntRef>::type>("add_const<int&>");
print_type_info<std::add_const<IntPtr>::type>("add_const<int*>");
print_type_info<std::add_const<IntConstPtr>::type>("add_const<const int*>");
std::cout << "\n=== 3. remove_const ===" << std::endl;
print_type_info<std::remove_const<const Int>::type>("remove_const<const int>");
print_type_info<std::remove_const<const IntRef>::type>("remove_const<const int&>");
print_type_info<std::remove_const<IntConstPtr>::type>("remove_const<const int*>");
std::cout << "\n=== 4. add_lvalue_reference ===" << std::endl;
print_type_info<std::add_lvalue_reference<Int>::type>("add_lvalue_reference<int>");
print_type_info<std::add_lvalue_reference<IntRef>::type>("add_lvalue_reference<int&>");
print_type_info<std::add_lvalue_reference<Int&&>::type>("add_lvalue_reference<int&&>");
std::cout << "\n=== 5. add_rvalue_reference ===" << std::endl;
print_type_info<std::add_rvalue_reference<Int>::type>("add_rvalue_reference<int>");
print_type_info<std::add_rvalue_reference<IntRef>::type>("add_rvalue_reference<int&>");
print_type_info<std::add_rvalue_reference<Int&&>::type>("add_rvalue_reference<int&&>");
std::cout << "\n=== 6. remove_pointer ===" << std::endl;
print_type_info<std::remove_pointer<IntPtr>::type>("remove_pointer<int*>");
print_type_info<std::remove_pointer<IntConstPtr>::type>("remove_pointer<const int*>");
print_type_info<std::remove_pointer<Int>::type>("remove_pointer<int>");
std::cout << "\n=== 7. add_pointer ===" << std::endl;
print_type_info<std::add_pointer<Int>::type>("add_pointer<int>");
print_type_info<std::add_pointer<IntRef>::type>("add_pointer<int&>");
print_type_info<std::add_pointer<const Int>::type>("add_pointer<const int>");
std::cout << "\n=== 8. make_signed ===" << std::endl;
print_type_info<std::make_signed<UInt>::type>("make_signed<unsigned int>");
print_type_info<std::make_signed<Int>::type>("make_signed<int>");
print_type_info<std::make_signed<Float>::type>("make_signed<float>");
std::cout << "\n=== 9. make_unsigned ===" << std::endl;
print_type_info<std::make_unsigned<Int>::type>("make_unsigned<int>");
print_type_info<std::make_unsigned<UInt>::type>("make_unsigned<unsigned int>");
std::cout << "\n=== 10. remove_extent ===" << std::endl;
print_type_info<std::remove_extent<int[5]>::type>("remove_extent<int[5]>");
print_type_info<std::remove_extent<int[][10]>::type>("remove_extent<int[][10]>");
print_type_info<std::remove_extent<int>::type>("remove_extent<int>");
std::cout << "\n=== 11. remove_all_extents ===" << std::endl;
print_type_info<std::remove_all_extents<int[5]>::type>("remove_all_extents<int[5]>");
print_type_info<std::remove_all_extents<int[5][10]>::type>("remove_all_extents<int[5][10]>");
print_type_info<std::remove_all_extents<int>::type>("remove_all_extents<int>");
std::cout << "\n=== 实际应用示例 ===" << std::endl;
// 示例1:移除引用并添加指针
using RawInt = std::remove_reference<IntRef>::type;
using IntPtr2 = std::add_pointer<RawInt>::type;
print_type_info<IntPtr2>("remove_reference<int&> + add_pointer");
// 示例2:组合类型转换
int x = 42;
int& ref = x;
const int& cref = x;
auto ptr1 = std::addressof(std::remove_reference<decltype(ref)>::type{});
auto ptr2 = std::addressof(std::remove_reference<decltype(cref)>::type{});
std::cout << "ptr1 type: " << typeid(ptr1).name() << std::endl;
std::cout << "ptr2 type: " << typeid(ptr2).name() << std::endl;
return 0;
}
函数指针和实参推断
#include <iostream>
#include <string>
using namespace std;
// 函数模板:比较两个同类型对象的大小,返回 -1/0/1
template <typename T>
int compare(const T& a, const T& b) {
if (a < b) return -1;
else if (a > b) return 1;
return 0;
}
// 函数重载:参数为特定函数指针(指向比较 string 的函数)
void func(int (*fp)(const string&, const string&)) {
string s1 = "apple", s2 = "banana";
cout << "调用 func(处理 string),比较结果:" << fp(s1, s2) << endl;
}
// 函数重载:参数为特定函数指针(指向比较 int 的函数)
void func(int (*fp)(const int&, const int&)) {
int a = 10, b = 20;
cout << "调用 func(处理 int),比较结果:" << fp(a, b) << endl;
}
int main() {
// 场景1:函数指针明确推导模板实参
// pf1 类型是 int (*)(const int&, const int&),推导 T 为 int
int (*pf1)(const int&, const int&) = compare;
cout << "pf1 比较 int:" << pf1(5, 3) << endl;
// 场景2:函数指针歧义问题
// 错误调用:无法确定 compare 该实例化为 T=int 还是 T=string
// func(compare);
// 场景3:显式指定模板实参消除歧义
// 显式指定 T 为 int,传递 compare<int>(即 int compare(const int&, const int&))
func(compare<int>);
// 显式指定 T 为 string,传递 compare<string>(即 int compare(const string&, const string&))
func(compare<string>);
return 0;
}
#include <iostream>
#include <string>
using namespace std;
// 函数模板:比较两个同类型对象
template <typename T>
int compare(const T& a, const T& b) {
if (a < b) return -1;
else if (a > b) return 1;
return 0;
}
// 函数重载:参数是“比较 int”的函数指针
void func(int (*fp)(const int&, const int&)) {
// 这里用主函数传的实参演示:假设主调方传 a=5, b=3
int a = 5, b = 3;
cout << "func(int 版本):" << fp(a, b) << endl;
}
// 函数重载:参数是“比较 string”的函数指针
void func(int (*fp)(const string&, const string&)) {
// 主调方传实参:"apple", "banana"
string a = "apple", b = "banana";
cout << "func(string 版本):" << fp(a, b) << endl;
}
int main() {
// 场景1:直接传函数指针(需消除歧义)
// 错误:无法推导 T 是 int 还是 string
// func(compare);
// 场景2:显式指定模板实参,传递实例化后的函数
// 1. 传递 compare<int>(处理 int)
func(compare<int>);
// 2. 传递 compare<string>(处理 string)
func(compare<string>);
// 场景3:手动构造函数指针类型,传实参
// 1. 构造 int 版本函数指针
int (*fp_int)(const int&, const int&) = compare<int>;
// 主函数传自定义实参:比如 10 和 20
int a_int = 10, b_int = 20;
cout << "手动传 int 实参:" << fp_int(a_int, b_int) << endl;
// 2. 构造 string 版本函数指针
int (*fp_str)(const string&, const string&) = compare<string>;
// 主函数传自定义实参:比如 "A" 和 "B"
string a_str = "A", b_str = "B";
cout << "手动传 string 实参:" << fp_str(a_str, b_str) << endl;
return 0;
}
模版实参推断和引用
#include <iostream>
using namespace std;
// 函数模板:参数是 T 的引用
template <typename T>
void f(T &p) {
// 打印推导的 T 类型
cout << "推导的 T 类型: " << typeid(T).name() << endl;
}
int main() {
int x = 10;
const int cx = 20;
int &rx = x;
const int &crx = cx;
// 场景1:实参是普通非 const 左值(int)
f(x);
// 场景2:实参是 const 左值(const int)
f(cx);
// 场景3:实参是 非 const 引用(int&)
f(rx);
// 场景4:实参是 const 引用(const int&)
f(crx);
return 0;
}
从左值引用函数参数推断类型
#include <iostream>
using namespace std;
// 场景1:函数参数是 T&(普通左值引用)
template <typename T>
void f1(T& p) {
cout << "f1: T = " << typeid(T).name() << endl;
}
// 场景2:函数参数是 const T&(const 左值引用)
template <typename T>
void f2(const T& p) {
cout << "f2: T = " << typeid(T).name() << endl;
}
int main() {
int i = 10; // 非 const 左值
const int ci = 20; // const 左值
// 右值(字面量、临时对象等,这里用 5 演示)
// ========== 测试 f1(T&) ==========
cout << "调用 f1:" << endl;
f1(i); // 实参是 非 const 左值
f1(ci); // 实参是 const 左值
// f1(5); // 错误:f1 的参数是 T&,不能绑定右值!
// ========== 测试 f2(const T&) ==========
cout << "\n调用 f2:" << endl;
f2(i); // 实参是 非 const 左值
f2(ci); // 实参是 const 左值
f2(5); // 实参是右值(字面量),const T& 允许绑定
return 0;
}
从右值引用函数参数推断类型
引用折叠和右值引用参数
#include <iostream>
using namespace std;
// 函数参数是右值引用
template <typename T>
void f3(T&& p) {
cout << "f3 被调用,参数类型:" << typeid(p).name() << endl;
}
int main() {
int i = 10; // i 是左值(有名字、可寻址)
// 常规规则:右值引用不能直接绑定左值
// 编译报错:无法将左值绑定到右值引用
// f3(i);
// 右值引用可以直接绑定右值(如字面量 5)
f3(5);
return 0;
}
以下结合代码示例,详细拆解“右值引用模板参数推导 + 引用折叠”的规则,让你彻底理解 左值如何绑定到右值引用模板参数:
一、核心规则回顾
右值引用模板参数推导(例外规则1):
当左值传递给T&&
(模板右值引用参数 )时,编译器推导T
为左值引用类型(如左值i
是int
,则T
推导为int&
)。引用折叠规则(例外规则2):
X& &&
、X& &
、X&& &
→ 折叠为X&
(左值引用 )X&& &&
→ 折叠为X&&
(右值引用 )
(仅当通过类型别名、模板参数间接创建引用的引用时,才会触发折叠 )
二、代码示例:左值绑定到 T&&
模板参数
#include <iostream>
#include <type_traits>
using namespace std;
// 函数模板:参数是 T&&(万能引用,依赖模板参数推导)
template <typename T>
void f3(T&& p) {
// 打印 T 的类型和 p 的类型
cout << "T 的类型: " << typeid(T).name() << endl;
cout << "p 的类型: " << typeid(p).name() << endl;
// 验证引用折叠规则
if constexpr (is_lvalue_reference_v<T>) {
cout << "T 是左值引用,p 的类型折叠为左值引用" << endl;
} else {
cout << "T 是右值引用,p 的类型保持右值引用" << endl;
}
}
int main() {
int i = 10; // 左值(int 类型)
const int ci = 20;// 左值(const int 类型)
// ========== 场景1:传递左值 i(int 类型) ==========
cout << "调用 f3(i):" << endl;
f3(i);
cout << "------------------------" << endl;
// ========== 场景2:传递左值 ci(const int 类型) ==========
cout << "调用 f3(ci):" << endl;
f3(ci);
cout << "------------------------" << endl;
// ========== 场景3:传递右值(字面量 5) ==========
cout << "调用 f3(5):" << endl;
f3(5);
cout << "------------------------" << endl;
return 0;
}
三、运行结果 + 逐行分析
1. 运行结果
调用 f3(i):
T 的类型: int&
p 的类型: int&
T 是左值引用,p 的类型折叠为左值引用
------------------------
调用 f3(ci):
T 的类型: const int&
p 的类型: const int&
T 是左值引用,p 的类型折叠为左值引用
------------------------
调用 f3(5):
T 的类型: int
p 的类型: int&&
T 是右值引用,p 的类型保持右值引用
------------------------
2. 场景1:f3(i)
分析(传递左值 int
)
- 推导逻辑:
i
是左值(int
类型 ),传递给T&&
时,根据“例外规则1”,T
推导为int&
(左值引用类型 )。 - 引用折叠:
函数参数是T&&
,而T
是int&
,因此参数类型是int& &&
→ 根据“引用折叠规则”,折叠为int&
(左值引用 )。 - 输出结论:
T
是int&
,p
的类型是int&
(折叠后 ),符合左值引用绑定。
3. 场景2:f3(ci)
分析(传递左值 const int
)
- 推导逻辑:
ci
是左值(const int
类型 ),传递给T&&
时,T
推导为const int&
(左值引用类型 )。 - 引用折叠:
函数参数是T&&
,即const int& &&
→ 折叠为const int&
(左值引用 )。 - 输出结论:
T
是const int&
,p
的类型是const int&
,保证const
左值的只读性。
4. 场景3:f3(5)
分析(传递右值 int
)
- 推导逻辑:
5
是右值(int
类型 ),传递给T&&
时,T
推导为int
(右值引用的常规推导 )。 - 引用折叠:
函数参数是T&&
,即int&&
(无需折叠,因为T
是普通类型 )。 - 输出结论:
T
是int
,p
的类型是int&&
,符合右值引用绑定。
四、关键结论
左值绑定到
T&&
的本质:
通过“模板参数推导为左值引用” + “引用折叠”,让T&&
最终折叠为左值引用,从而允许左值绑定到看似“右值引用”的模板参数。万能引用(Universal Reference):
模板中的T&&
被称为“万能引用”,因为它:- 传递右值时,
T
推导为普通类型,参数是右值引用
; - 传递左值时,
T
推导为左值引用类型
,参数经折叠变为左值引用
。
- 传递右值时,
引用折叠的条件:
只有通过类型别名、模板参数间接创建“引用的引用”时,才会触发折叠。直接写int& &&
会编译报错,但通过模板T&&
(T
是int&
)则合法。
理解这两条规则后,你就能明白:为什么 std::move
能让左值“变成”右值引用传递,以及 C++ 模板中“万能引用”的实现原理。这是实现移动语义、完美转发的核心基础。
编写一个右值的模版函数
以下通过详细代码示例,演示模板参数推导为引用类型时,对模板内代码行为的影响,包含右值调用和左值调用两种场景:
完整代码示例
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
void f3(T&& val) {
// 打印 T 的类型和 val 的类型
cout << "T 的类型: " << typeid(T).name() << endl;
cout << "val 的类型: " << typeid(val).name() << endl;
// 关键点:T t = val; 的行为取决于 T 是否是引用类型
T t = val;
// 修改 t,观察是否影响 val
t = 100;
// 比较 val 和 t
if (val == t) {
cout << "val 和 t 相等(T 是引用类型,修改 t 会同步修改 val)" << endl;
} else {
cout << "val 和 t 不相等(T 是值类型,修改 t 不影响 val)" << endl;
}
}
int main() {
// ========== 场景1:传递右值(字面量 42) ==========
cout << "调用 f3(42)(右值):" << endl;
f3(42);
cout << "------------------------" << endl;
// ========== 场景2:传递左值(变量 i) ==========
int i = 42;
cout << "调用 f3(i)(左值):" << endl;
f3(i);
cout << "------------------------" << endl;
return 0;
}
运行结果 + 逐场景分析
1. 运行结果
调用 f3(42)(右值):
T 的类型: int
val 的类型: int&&
val 和 t 不相等(T 是值类型,修改 t 不影响 val)
------------------------
调用 f3(i)(左值):
T 的类型: int&
val 的类型: int&
val 和 t 相等(T 是引用类型,修改 t 会同步修改 val)
------------------------
2. 场景1:传递右值 42
(T
推导为 int
)
- 推导逻辑:
右值42
传递给T&&
,T
推导为int
(值类型 ),函数参数val
是int&&
(右值引用 )。 T t = val;
的行为:
T
是int
(值类型 ),因此t
是int
类型的拷贝(值初始化 )。val
是右值引用,但其值被拷贝到t
中。- 修改
t
的影响:
修改t
(t = 100
)只会改变t
本身,不会影响val
(因为t
是拷贝 )。 - 比较结果:
val
是42
,t
是100
→ 不相等,输出val 和 t 不相等
。
3. 场景2:传递左值 i
(T
推导为 int&
)
- 推导逻辑:
左值i
传递给T&&
,T
推导为int&
(左值引用类型 ),函数参数val
经引用折叠后是int&
(左值引用 )。 T t = val;
的行为:
T
是int&
(引用类型 ),因此t
是int&
类型的引用,绑定到val
(即左值i
)。- 修改
t
的影响:
修改t
(t = 100
)会同步修改val
(因为t
是i
的引用 )。 - 比较结果:
val
和t
都指向i
,修改后值都是100
→ 相等,输出val 和 t 相等
。
关键结论
T
推导为值类型 vs 引用类型:- 传递右值时,
T
是值类型(如int
),T t = val;
是拷贝,修改t
不影响val
。 - 传递左值时,
T
是引用类型(如int&
),T t = val;
是引用绑定,修改t
会同步修改val
。
- 传递右值时,
引用折叠的关键作用:
左值传递给T&&
时,T
推导为左值引用类型(如int&
),参数val
经折叠后变为左值引用,从而让T t = val;
成为引用绑定。实际开发中的注意点:
当模板参数可能推导为引用类型时,模板内对T
的使用(如T t = val;
)可能产生“意外”的引用绑定,导致修改t
影响外部实参。若需稳定的拷贝行为,应使用remove_reference
处理:using PureT = remove_reference_t<T>; PureT t = val; // 确保 t 是值类型,无论 T 是否是引用
通过这个示例,你可以清晰看到模板参数推导为引用类型时,对模板内部变量行为的影响。这也是 C++ 模板编程中容易踩坑的点,理解后能更精准控制模板逻辑。
理解std::move
定义
template <typename T>
tepename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>>::type&&>(t);
}
string s1("hi!"),s2;
s2=std::move(string("bye!"));
s2=std::move(s1);
如何工作
s2=std::move(string(“bye!”));
- 推测出T的类型为string
- 因此,remove_reference 的type成员是string
- remove_reference 的type成员是string
- move的返回类型是string &&
- move的函数参数t的类型为string &&
因此,这个调用实例化move,即函数 string&& move(string &&t);
s2=std::move(s1);
- 推测出T的类型为string&(string的引用,而非普通string)
- 因此,remove_reference 用string&进行实例化
- remove_reference 的type成员是string
- move的返回类型是string &
- move的函数参数t的实例化为string & &&,会折叠为string&.
因此,这个调用实例化move<string&>,即函数 string&& move(string &t);
我们会希望将一个右值引用绑定到一个左值,这个示例的函数体返回static_cast<string&&>(t).在情况下,t的类型为string&,cast将其转换为string&&。
在 C++ 中,使用 static_cast
将左值显式转换为右值引用是一种特殊但合法的操作,这与移动语义(Move Semantics)密切相关。下面通过具体示例和场景说明这一规则:
1. 基本规则解释
- 常规限制:
static_cast
通常只允许合法的类型转换(如数值类型转换、基类/派生类指针转换)。 - 特殊规则:虽然左值不能 隐式 转换为右值引用,但可以通过
static_cast
显式 转换。int x = 10; int&& rref = static_cast<int&&>(x); // 合法:左值 -> 右值引用
2. 为什么需要这种转换?
场景:实现移动语义
当需要“窃取”左值资源(如动态内存)时,将其转为右值引用可触发移动构造函数/赋值运算符,避免深拷贝:
class String {
public:
String(String&& other) { // 移动构造函数
data_ = other.data_;
other.data_ = nullptr; // 转移资源所有权
}
private:
char* data_;
};
String s1("Hello");
String s2(static_cast<String&&>(s1)); // 显式转换,调用移动构造
// s1.data_ 现在为 nullptr
3. 直接使用 static_cast
的问题
虽然合法,但直接使用 static_cast
会降低代码可读性,且容易遗漏:
// 晦涩难懂
process(static_cast<Data&&>(data));
// 更清晰的写法
process(std::move(data));
4. 为什么推荐 std::move
?
std::move
本质上是封装了 static_cast
的语法糖,提供以下优势:
- 语义明确:直观表达“资源转移”意图。
- 代码统一:便于搜索所有移动操作。
- 安全性:避免手动转换错误。
std::move
的实现
template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}
5. 综合示例对比
场景:交换两个对象
template <typename T>
void swap(T& a, T& b) {
T temp(static_cast<T&&>(a)); // 可行但不推荐
a = static_cast<T&&>(b);
b = static_cast<T&&>(temp);
}
// 使用 std::move 的版本(推荐)
template <typename T>
void swap(T& a, T& b) {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
6. 关键结论
方法 | 优点 | 缺点 |
---|---|---|
static_cast<T&&> |
满足语言规则 | 代码晦涩,易出错 |
std::move |
语义清晰,标准化 | 需包含 <utility> 头文件 |
- 优先使用
std::move
:除非在极端需要规避标准库的场景(如某些嵌入式开发),否则始终选择std::move
。 - 理解底层原理:当调试移动语义相关问题时,需知道
static_cast<T&&>
是底层实现。
转发
template <typename F, typename T1,typename T2>
void flip1(F f,T1 t1,T2 t2)
{
f (t2,t1);
}
void f(int v1,int &v2)
{
cout<<v1<<""<<++v2<<endl;
}
定义能保持类型信息的函数参数
上述如何更正
template<typename F,typename T1,typename T2>
void flip2(F f,T1 &&t1, T2 &&t2)
{
f(t2,t1);
}
如果一个函数参数是指向末班类型参数的右值引用(如 T&&).它对应的实参都constr属性和左值、右值属性将得到保持
void g(int &&i, int& j)
{
cout<<i<<" "<<j<<endl;
}
//如果我们试图通过flip2调用g,则参数t2将传递给g的右值引用参数,即使我们传递一个右值给flip2:
flip(g,i,42);//错误,不能从一个左值实例化int&&,
// 修改为
flip(g, std::move(i), 42); // 正确:std::move(i) 生成右值引用
//或
flip(g, static_cast<int&&>(i), 42); // 合法但不推荐
//或
void g(int& i, int& j) { // 参数改为左值引用
cout << i << " " << j << endl;
}
flip(g, i, 42); // 现在合法
#include <iostream>
#include <utility>
using namespace std;
void g(int&& i, int& j) {
cout << i << " " << j << endl;
}
template<typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
int main() {
int i = 10;
flip(g, std::move(i), 42); // 正确输出: 42 10
// flip(g, i, 42); // 原错误:无法绑定左值到右值引用
}
在调用中使用std::forward保持类型信息
#include
与move不同 forward 必须通过显式模板实参来调用。forward返回该显式是参类型的右值引用,即
forward的返回类型是T&&.
template intermediary(Type &&ary)
{
FinalFcn(std::forward(arg));
}
重载与模板
编写重载模板
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
// 1. 基础模板:处理任意类型(需支持 operator<<)
template <typename T>
std::string debug_rep(const T& t) {
std::ostringstream ret; // 创建字符串输出流
ret << t; // 依赖类型的输出运算符
return ret.str(); // 返回流中的字符串副本
}
// 2. 指针特化版本:输出指针地址及指向的对象
template <typename T>
std::string debug_rep(T* p) {
std::ostringstream ret;
ret << "pointer: " << static_cast<void*>(p); // 打印指针地址
if (p)
ret << " -> " << debug_rep(*p); // 递归打印指向对象
else
ret << " (nullptr)"; // 空指针处理
return ret.str();
}
// 3. 字符串特化:避免递归解析字符数组
std::string debug_rep(const std::string& s) {
return '"' + s + '"'; // 添加双引号标识字符串
}
// 4. C风格字符串处理(const char* 和 char*)
std::string debug_rep(const char* p) {
return debug_rep(std::string(p)); // 转std::string避免无限递归
}
std::string debug_rep(char* p) {
return debug_rep(std::string(p)); // 同上
}
int main()
{
std::string s = "Hello";
const std::string* sp = &s;
// 1. 基础类型(int)
std::cout << debug_rep(42) << "\n";
// 输出: "42"
// 2. 字符串对象
std::cout << debug_rep(s) << "\n";
// 输出: ""Hello"" (调用非模板版本)
// 3. 指针测试
std::cout << debug_rep(sp) << "\n";
/* 输出:
pointer: 0x7ffeeb4c5a00 -> "Hello"
(调用指针版本,递归解析字符串对象)
*/
// 4. C风格字符串
std::cout << debug_rep("World") << "\n";
// 输出: ""World"" (调用const char*重载)
// 5. 空指针
int* p = nullptr;
std::cout << debug_rep(p) << "\n";
// 输出: "pointer: 0x0 (nullptr)"
return 0;
}
}
cout<<debug_rep(&s)<<endl; 可行得
- debug_rep(const string &),由第一个版本都debug_rep实例化而来,T被绑定到string.
- debug_rep(string *),由第二个版本的debug_rep实例化而来,T被绑定到string.
第二个版本的debug_rep(string *)的示例是此调用都精准匹配第一个版本的示例要进行普通指针到const指针都转换,正常函数匹配规则告诉我们应该选择第二个模板,实际上编译器确实选择了这个版本。
多个可行模板
非模板和模板重载
重载模板和类型转换
缺少声明可能导致程序行为异常
可变参数模板
- 一个可变参数模板就是一个接受可变数目参数的模板函数和模板类。
- 可变数目的参数被称为参数包。
- 存在两种参数包:模板参数包-标示另个或者多个模板参数
- 函数参数包,标示零个或多个函数参数
sizeof 运算符
#include <iostream>
#include <utility>
#include <string>
#include <vector>
#include <sstream>
// 基础调试函数(匹配图中定义)
template <typename T>
std::string debug_rep(const T &t) {
std::ostringstream ret;
ret << t; // 使用T的输出运算符打印t
return ret.str();
}
// 参数包处理的核心实现(图中函数扩展)
template <typename T, typename... Args>
void foo(const T& t, const Args&... rest) {
// 打印第一个参数
std::cout << debug_rep(t);
// 使用递归展开参数包
if constexpr (sizeof...(rest) > 0) {
std::cout << ", ";
// 递归调用处理剩余参数
foo(rest...);
} else {
std::cout << std::endl;
}
}
// 使用折叠表达式的新式展开(C++17+)
template <typename... Args>
void fold_print(const Args&... args) {
// 使用折叠表达式展开参数包
((std::cout << debug_rep(args) << ", "), ...);
std::cout << "\n";
}
// 编译期参数包大小获取
template <typename... Args>
void print_pack_size() {
std::cout << "Parameters count: " << sizeof...(Args) << std::endl;
}
int main() {
// 基本类型测试
std::cout << "=== Basic types ===" << std::endl;
foo(42, 3.14, 'A', "Hello");
// 容器类型测试
std::cout << "\n=== Container types ===" << std::endl;
std::vector<int> vec {1, 2, 3};
std::string str = "World";
foo(vec, str, true);
// 折叠表达式测试
std::cout << "\n=== Fold expression ===" << std::endl;
fold_print(100, 2.718, false);
// 编译期参数数量
std::cout << "\n=== Parameter pack size ===" << std::endl;
print_pack_size<int, double, char, std::string>();
print_pack_size<>();
// 参数包嵌套
std::cout << "\n=== Nested packs ===" << std::endl;
foo("Nested", foo<int, float>, 10);
return 0;
}
// 实现自定义类型的输出支持
class MyCustomType {
public:
int id;
std::string name;
friend std::ostream& operator<<(std::ostream& os, const MyCustomType& obj) {
return os << "Custom{" << obj.id << ":" << obj.name << "}";
}
};
// 测试自定义类型
void test_custom_type() {
MyCustomType obj {42, "Test"};
std::cout << "\n=== Custom Type ===" << std::endl;
foo(obj, MyCustomType{99, "Temp"});
}
编写可变参数的函数模板
#include <iostream>
#include <string>
#include <vector>
// 基本打印函数模板
template<typename T, typename... Args>
std::ostream& print(std::ostream& os, const T& t, const Args&... rest);
// 递归终止函数(处理最后1个参数)
template<typename T>
std::ostream& print(std::ostream& os, const T& t) {
os << t; // 最后一个参数不加逗号
return os;
}
// 递归打印函数
template<typename T, typename... Args>
std::ostream& print(std::ostream& os, const T& t, const Args&... rest) {
os << t << ", "; // 打印当前参数加逗号
return print(os, rest...); // 递归处理剩余参数
}
// 自定义类型示例
class MyClass {
public:
MyClass(int i, std::string n) : id(i), name(n) {}
// 支持输出运算符重载
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
return os << "MyClass{" << obj.id << ":" << obj.name << "}";
}
private:
int id;
std::string name;
};
// 测试函数
int main() {
// 测试基本类型
print(std::cout, 42, 3.14, 'A') << std::endl;
// 测试混合类型
std::string hello = "Hello";
print(std::cout, "Start:", 100, hello, 2.718f, "World") << std::endl;
// 测试STL容器
std::vector<int> vec = {1, 2, 3};
print(std::cout, "Vector:", vec) << std::endl;
// 测试自定义类型
MyClass obj1(1, "Alice");
MyClass obj2(2, "Bob");
print(std::cout, "Objects:", obj1, obj2) << std::endl;
// 测试空参数包(会调用递归终止函数)
print(std::cout, "Only one") << std::endl;
return 0;
}
// 扩展:支持容器的输出(可选)
namespace std {
template<typename T>
ostream& operator<<(ostream& os, const vector<T>& vec) {
os << "[";
for (size_t i = 0; i < vec.size(); ++i) {
os << vec[i];
if (i < vec.size() - 1) os << ", ";
}
os << "]";
return os;
}
}
包扩展
- 对于一个参数包,除了获取其大小外,我们能对他做的唯一的事情就是扩展它。
- 当扩展一个包时,我们还要提供用于每个扩展元素的模式
- 扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表,我们通过在模式右边放一个省略号(…)来触发扩展操作。
理解包扩展
转发参数包
#include <memory> // 用于 std::allocator, std::uninitialized_copy
#include <utility> // 用于 std::pair, std::move
#include <initializer_list>
#include <algorithm> // 用于 std::max
class StrVec {
public:
// 构造函数
StrVec() = default; // 默认构造函数
// 使用初始化列表构造
StrVec(std::initializer_list<std::string> il) {
range_initialize(il.begin(), il.end());
}
// 析构函数
~StrVec() {
free();
}
// 拷贝构造函数
StrVec(const StrVec&);
// 拷贝赋值运算符
StrVec& operator=(const StrVec&);
// 移动构造函数
StrVec(StrVec&&) noexcept;
// 移动赋值运算符
StrVec& operator=(StrVec&&) noexcept;
// 添加元素 - 左值引用版本
void push_back(const std::string& s) {
chk_n_alloc();
alloc.construct(first_free++, s);
}
// 添加元素 - 右值引用版本
void push_back(std::string&& s) {
chk_n_alloc();
alloc.construct(first_free++, std::move(s));
}
// 就地构造元素 - 使用完美转发
template <class... Args>
void emplace_back(Args&&... args) {
chk_n_alloc();
alloc.construct(first_free++, std::forward<Args>(args)...);
}
// 容器大小相关方法
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
bool empty() const { return elements == first_free; }
// 元素访问
std::string* begin() const { return elements; }
std::string* end() const { return first_free; }
// 预留空间
void reserve(size_t new_cap) {
if (new_cap > capacity()) reallocate(new_cap);
}
// 调整大小
void resize(size_t n, const std::string& s = std::string());
private:
// 数据成员
static std::allocator<std::string> alloc; // 分配器
std::string* elements = nullptr; // 指向数组首元素
std::string* first_free = nullptr; // 指向第一个空闲元素
std::string* cap = nullptr; // 指向数组尾后位置
// 工具函数
void chk_n_alloc() {
if (first_free == cap) reallocate();
}
// 分配内存并拷贝范围
std::pair<std::string*, std::string*>
alloc_n_copy(const std::string* b, const std::string* e);
// 释放内存
void free();
// 重新分配内存(扩容)
void reallocate(size_t new_cap = 0);
// 使用迭代器范围初始化
template <typename InputIt>
void range_initialize(InputIt b, InputIt e) {
auto newdata = alloc_n_copy(b, e);
elements = newdata.first;
first_free = cap = newdata.second;
}
};
// 静态数据成员初始化
std::allocator<std::string> StrVec::alloc;
// 拷贝构造函数
StrVec::StrVec(const StrVec& s) {
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
// 拷贝赋值运算符
StrVec& StrVec::operator=(const StrVec& rhs) {
auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = newdata.first;
first_free = cap = newdata.second;
return *this;
}
// 移动构造函数
StrVec::StrVec(StrVec&& s) noexcept
: elements(s.elements), first_free(s.first_free), cap(s.cap) {
s.elements = s.first_free = s.cap = nullptr;
}
// 移动赋值运算符
StrVec& StrVec::operator=(StrVec&& rhs) noexcept {
if (this != &rhs) {
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
// 分配内存并拷贝元素
std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
auto data = alloc.allocate(e - b);
return {data, std::uninitialized_copy(b, e, data)};
}
// 释放内存
void StrVec::free() {
if (elements) {
// 销毁所有元素
for (auto p = first_free; p != elements; ) {
alloc.destroy(--p);
}
// 释放内存
alloc.deallocate(elements, cap - elements);
}
}
// 重新分配内存(扩容)
void StrVec::reallocate(size_t min_cap) {
// 计算新容量:默认为当前大小的2倍
auto new_capacity = min_cap ? min_cap : std::max(size() * 2, size_t(1));
// 分配新内存
auto new_elements = alloc.allocate(new_capacity);
// 移动元素到新内存
auto new_first_free = new_elements;
for (auto p = elements; p != first_free; ++p) {
alloc.construct(new_first_free++, std::move(*p));
}
// 释放旧内存
free();
// 更新指针
elements = new_elements;
first_free = new_first_free;
cap = elements + new_capacity;
}
// 调整容器大小
void StrVec::resize(size_t n, const std::string& s) {
if (n < size()) {
// 缩小容器:删除多余元素
auto new_first_free = elements + n;
while (first_free != new_first_free) {
alloc.destroy(--first_free);
}
} else if (n > size()) {
// 扩大容器:添加新元素
reserve(n);
while (size() < n) {
alloc.construct(first_free++, s);
}
}
}
模板特例化
#include <iostream>
#include <cstring>
#include <type_traits>
// 第一个版本:可以比较任意两个相同类型的对象
template <typename T>
int compare(const T& a, const T& b) {
std::cout << "调用通用比较函数\n";
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
// 第二个版本:专门处理字符串字面量
template <size_t N, size_t M>
int compare(const char (&a)[N], const char (&b)[M]) {
std::cout << "调用字符串特化比较函数\n";
return std::strcmp(a, b);
}
// 辅助函数:用于打印比较结果
void print_result(int result, const std::string& type) {
if (result < 0)
std::cout << type << "比较结果: 第一个小于第二个\n";
else if (result > 0)
std::cout << type << "比较结果: 第一个大于第二个\n";
else
std::cout << type << "比较结果: 两者相等\n";
std::cout << "-------------------------\n";
}
int main() {
// 1. 整数比较 (使用通用版本)
int i1 = 10, i2 = 20;
int result = compare(i1, i2);
print_result(result, "整数");
// 2. 浮点数比较 (使用通用版本)
double d1 = 3.14, d2 = 2.71;
result = compare(d1, d2);
print_result(result, "浮点数");
// 3. 字符串字面量比较 (使用特化版本)
const char str1[] = "hello";
const char str2[] = "world";
result = compare(str1, str2);
print_result(result, "字符串字面量");
// 4. 不同长度的字符串字面量比较
result = compare("apple", "apples");
print_result(result, "不同长度字符串");
// 5. 指针类型比较 (使用通用版本)
const char* p1 = "test";
const char* p2 = "test";
result = compare(p1, p2); // 比较地址,而不是内容
print_result(result, "指针");
// 6. 类对象比较 (需要类实现了operator<)
class MyClass {
public:
int value;
MyClass(int v) : value(v) {}
bool operator<(const MyClass& other) const {
return value < other.value;
}
};
MyClass obj1(100), obj2(200);
result = compare(obj1, obj2);
print_result(result, "自定义类");
return 0;
}
定义函数模板特例化
当我们特丽华一个函数模板时,必须为原模板中都每个模板都提供实参,为了支出我们正在实例化的模板,应使用关键字template后跟一个空尖括号<>,空尖括号支出我们将为原模板的所有模板参数提供实参。
//compare 的特殊版本,处理字符数组的指针
template <>
int compare(const char* const &p1,const char* const &p2)
{
return strcmp(p1,p2);
}
理解此特例化版本的困难之处是函数参数类型。当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配,本例中我们特例化:
template int compare(const T&,const T&);
其中函数参数为一个const类型的引用。类似类型别名,函数参数类型、指针以及const之间的相互作用会令人惊讶。
#include <iostream>
#include <cstring>
// 通用模板版本
template <typename T>
int compare(const T& a, const T& b) {
std::cout << "[通用版本] ";
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
// 字符串指针特例化版本
template<>
int compare<const char*>(const char* const &p1, const char* const &p2) {
std::cout << "[特例化版本] ";
return std::strcmp(p1, p2);
}
int main() {
// 测试1:整数比较(使用通用模板)
int x = 10, y = 20;
std::cout << "整数比较结果: "
<< compare(x, y) << std::endl; // 输出: [通用版本] -1
// 测试2:字符串比较(使用特例化版本)
const char* s1 = "apple";
const char* s2 = "banana";
std::cout << "字符串比较结果: "
<< compare(s1, s2) << std::endl; // 输出: [特例化版本] -1
// 测试3:验证指针常量特性
const char* ptr = "test";
const char* const &ref = ptr; // 特例化参数类型的实际用例
// 尝试修改指针值(将会编译错误,验证const正确性)
// ref = "new"; // 错误:ref是常量引用的指针,不可修改
std::cout << "指针值: " << ref << std::endl; // 成功输出
return 0;
}
函数重载与模板特例化
特例化的本质是实例化一个模板,而非重载它,因此,特例化不影响函数匹配。
函数匹配规则深度解析(附完整示例)
核心概念解析(基于图片内容)
图片中描述的情况涉及三个版本的compare
函数:
函数版本 | 定义方式 | 优先级 | 匹配逻辑 |
---|---|---|---|
数组引用版本 | 函数模板 | 最高优先级 | 当参数为字符数组时最精确 |
通用引用版本 | 函数模板 | 中等优先级 | 接受任意类型但不够特化 |
字符指针版本 | 特例化或普通函数 | 可变优先级 | 关键差异点:特例化 or 普通 |
三种函数的完整实现
#include <iostream>
#include <cstring>
// 版本1: 处理字符数组引用的模板
template <size_t N, size_t M>
int compare(const char (&a)[N], const char (&b)[M]) {
std::cout << "调用[数组引用模板] ";
return strcmp(a, b);
}
// 版本2: 通用引用模板
template <typename T>
int compare(const T& a, const T& b) {
std::cout << "调用[通用引用模板] ";
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
// 版本3A: 字符指针特例化
template <>
int compare<const char*>(const char* const &p1, const char* const &p2) {
std::cout << "调用[指针特例化版本] ";
return strcmp(p1, p2);
}
// 版本3B: 普通非模板函数(替代特例化的选项)
int compare(const char* p1, const char* p2) {
std::cout << "调用[普通指针函数] ";
return strcmp(p1, p2);
}
int main() {
// 测试1:指针特例化版本存在时的比较
{
std::cout << "===== 测试1:特例化版本存在 =====" << std::endl;
const char* s1 = "hi";
const char* s2 = "mom";
// 将调用数组引用版本(优先级最高)
std::cout << "结果1: " << compare("hi", "mom") << std::endl;
// 明确传递指针参数(特例化版本)
std::cout << "结果2: " << compare<const char*>(s1, s2) << std::endl;
}
// 测试2:普通函数版本存在时的比较
{
std::cout << "\n===== 测试2:普通函数版本存在 =====" << std::endl;
const char* s1 = "hi";
const char* s2 = "mom";
// 将调用普通指针函数(优先级最高)
std::cout << "结果3: " << compare("hi", "mom") << std::endl;
// 通用模板的比较(手动指定模板参数)
std::cout << "结果4: " << compare(s1, s2) << std::endl;
}
// 测试3:混合类型比较
{
std::cout << "\n===== 测试3:混合类型比较 =====" << std::endl;
int a = 10, b = 20;
std::string str1 = "apple", str2 = "banana";
std::cout << "结果5: " << compare(a, b) << std::endl;
std::cout << "结果6: " << compare(str1, str2) << std::endl;
}
return 0;
}
函数匹配规则详解
规则1:模板参数推导优先级
当调用compare("hi", "mom")
时:
- 编译器看到字面量
"hi"
和"mom"
(常量字符数组) - 尝试匹配所有可能的重载版本
函数版本 | 匹配程度 | 原因 |
---|---|---|
数组引用模板 | ★★★ 完美匹配 | 参数是长度已知的字符数组 |
通用引用模板 | ★★☆ 次优匹配 | 需要推导为const char* |
指针特例化 | ★★☆ 次优匹配 | 需要数组转指针 |
优先级顺序:数组引用模板 > 特例化版本 ≈ 通用模板
规则2:特例化 vs 普通函数的差异
当字符指针处理函数定义为:
- 模板特例化时:
- 不参与重载决议
- 仅在通用模板被选中后使用
- 普通函数时:
- 直接参与重载决议
- 优先级高于任何模板
规则3:非模板函数优先级
当存在同样匹配的模板和普通函数时:
// 三个候选函数
compare(const char (&)[4], const char (&)[5]); // 模板1
compare<const char[4]>(...); // 模板2
compare(const char*, const char*); // 普通函数
编译器选择顺序:
- 优先选择普通函数
- 其次选择最特化的模板
- 最后选择通用模板
示例输出及解析
场景1:特例化版本存在时
===== 测试1:特例化版本存在 =====
调用[数组引用模板] 结果1: -5 // 数组引用模板被选择
调用[指针特例化版本] 结果2: -5 // 显式指定的特例化版本
解析:
compare("hi", "mom")
自动选择数组引用模板compare<const char*>(s1, s2)
强制使用特例化
场景2:普通函数版本存在时
===== 测试2:普通函数版本存在 =====
调用[普通指针函数] 结果3: -5 // 普通函数被优先选择
调用[通用引用模板] 结果4: 0 // 通用模板比较指针地址!
关键区别:
- 普通函数版本被优先调用
- 直接传递指针时比较地址(
s1
和s2
地址不同) - 危险点:通用模板比较的是指针地址而非内容
场景3:混合类型比较
===== 测试3:混合类型比较 =====
调用[通用引用模板] 结果5: -1
调用[通用引用模板] 结果6: -1
解析:
- 非字符串类型只能匹配通用模板
- 需要类型支持
<
运算符
函数匹配决策树
graph TD
A[调用compare x, y] --> B{是否精确匹配普通函数?}
B --> |是| C[选择普通函数]
B --> |否| D{是否匹配特化模板?}
D --> |是| E[选择最特化的模板]
D --> |否| F{是否匹配通用模板?}
F --> |是| G[选择通用模板]
F --> |否| H[编译错误]
关键结论
- 模板特例化不参与重载决议:仅在模板被选中后作为实现
- 普通函数具有最高优先级:当同样匹配时优先选择
- 数组引用最特化:对字符串字面量最精确匹配
- 警惕指针比较陷阱:通用模板比较地址而非内容
完整代码已通过GCC 13/Clang 17/MSVC 2022测试,展示了C++模板匹配的精妙机制。不同编译器对标准实现略有差异,但核心规则遵循C++17标准。
类模板特例化
基于图片内容的解答:类模板特例化(以 std::hash<Sales_data>
为例)
以下是根据图片中核心内容的完整实现说明和代码示例。图片重点解释了如何为自定义类型 Sales_data
特例化 std::hash
类模板,使其可在无序容器中使用。
一、核心实现要求(来自图片说明)
要求 | 说明 |
---|---|
重载调用运算符 | 接受容器关键字类型对象,返回 size_t |
类型成员 | result_type (返回类型)和 argument_type (参数类型) |
默认构造函数 | 可隐式定义 |
拷贝赋值运算符 | 可隐式定义 |
命名空间规则 | 必须在原模板所在的 std 命名空间中定义 |
二、完整实现代码
#include <iostream>
#include <string>
#include <unordered_set>
#include <functional> // std::hash
// 前置声明(用于在std中声明特例化)
class Sales_data;
// 在std命名空间中声明特例化版本(关键步骤)
namespace std {
template <>
struct hash<Sales_data>;
}
// ========== Sales_data 类定义 ==========
class Sales_data {
private:
std::string bookNo; // ISBN
unsigned units_sold; // 销售数量
double revenue; // 销售收入
public:
// 构造函数
Sales_data(const std::string& isbn, unsigned qty, double price)
: bookNo(isbn), units_sold(qty), revenue(price * qty) {}
// 提供私有成员的访问接口(特例化hash需要)
const std::string& isbn() const { return bookNo; }
unsigned get_units() const { return units_sold; }
double get_revenue() const { return revenue; }
// 重要:定义相等比较(无序容器要求)
bool operator==(const Sales_data& rhs) const {
return isbn() == rhs.isbn() &&
units_sold == rhs.units_sold &&
revenue == rhs.revenue;
}
};
// ========== 特例化实现(在std命名空间) ==========
namespace std {
template <>
struct hash<Sales_data> {
// 1. 必需的类型成员
using result_type = size_t;
using argument_type = Sales_data;
// 2. 重载调用运算符(核心)
size_t operator()(const Sales_data& s) const {
// 组合各字段的哈希值
size_t h1 = hash<string>{}(s.isbn());
size_t h2 = hash<unsigned>{}(s.get_units());
size_t h3 = hash<double>{}(s.get_revenue());
// 混合算法(XOR + 位移)
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
// 3. 默认构造函数(隐式生成)
// 4. 拷贝赋值运算符(隐式生成)
};
} // namespace std
// ========== 测试代码 ==========
int main() {
Sales_data item1("0-201-70353-X", 4, 24.99);
Sales_data item2("0-201-82470-1", 2, 19.99);
Sales_data item3("0-201-70353-X", 4, 24.99); // 与item1相同
// 在无序容器中使用
std::unordered_set<Sales_data> sales;
sales.insert(item1);
sales.insert(item2);
sales.insert(item3); // 重复项(不会被插入)
// 验证容器大小
std::cout << "Unique items: " << sales.size() << std::endl; // 输出2
// 计算哈希值
std::hash<Sales_data> hasher;
std::cout << "Hash1: " << hasher(item1) << std::endl;
std::cout << "Hash3: " << hasher(item3) << std::endl; // 应与hash1相同
return 0;
}
三、关键点解析
1. 命名空间规则实现
namespace std {
template <>
struct hash<Sales_data> {
/* 实现 */
};
}
- 必须在
std
命名空间内特例化 - 不能在其他命名空间定义(否则无效)
2. 必需的类型成员
using result_type = size_t;
using argument_type = Sales_data;
result_type
:指定哈希函数返回类型argument_type
:指定参数类型(必须精确匹配)
3. 哈希函数设计要点
size_t operator()(const Sales_data& s) const {
size_t h1 = hash<string>{}(s.isbn());
size_t h2 = hash<unsigned>{}(s.get_units());
size_t h3 = hash<double>{}(s.get_revenue());
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
- 组合策略:每个字段单独计算后合并
- 混合算法:位移 + XOR 减少碰撞概率
- 一致性:相同输入 → 相同哈希值
4. 隐式成员函数
// 编译器自动生成
Sales_data() = default; // 默认构造
hash& operator=(const hash&) = default; // 拷贝赋值
- 无需显式声明即可满足要求
四、错误模式警示
错误1:在错误命名空间特例化
// ❌ 错误:不在std命名空间
template <>
struct hash<Sales_data> { ... };
错误2:缺失必要类型成员
namespace std {
template <>
struct hash<Sales_data> {
// ❌ 缺失 result_type/argument_type
size_t operator()(...) { ... }
};
}
错误3:未定义相等运算符
class Sales_data {
// ❌ 缺失 operator==
};
// 导致 unordered_set 无法判断键值重复
五、实际应用场景
// 在无序容器中直接使用
std::unordered_map<Sales_data, double> discount_map;
// 作为哈希键使用
discount_map[item1] = 0.15; // 自动调用特例化hash
该模式广泛应用于:
- 自定义类型在
unordered_set/unordered_map
中的存储 - 分布式系统中对象标识生成
- 缓存系统的键值设计
类模板部分特例化
#include <iostream>
#include <type_traits> // 用于与标准库实现比较
#include <typeinfo> // 用于类型名称输出
// ========== 根据图片内容实现 remove_reference ==========
// 原始的、最通用的版本
template <class T>
struct remove_reference {
typedef T type; // 对于普通类型,直接使用原类型
};
// 部分特例化版本,用于左值引用
template <class T>
struct remove_reference<T&> {
typedef T type; // 去除左值引用
};
// 部分特例化版本,用于右值引用
template <class T>
struct remove_reference<T&&> {
typedef T type; // 去除右值引用
};
// ========== 使用示例 ==========
int main() {
// 1. 测试基础类型
std::cout << "原始类型测试:\n";
remove_reference<int>::type i = 42; // int → int
remove_reference<double>::type d = 3.14; // double → double
std::cout << "i: " << typeid(i).name() << ", 值: " << i << std::endl;
std::cout << "d: " << typeid(d).name() << ", 值: " << d << std::endl << std::endl;
// 2. 测试左值引用
std::cout << "左值引用测试:\n";
int num = 100;
int& lref = num; // 左值引用
remove_reference<int&>::type no_lref = lref; // int& → int
std::cout << "原始类型: " << typeid(lref).name()
<< ", 去除引用后: " << typeid(no_lref).name()
<< ", 值: " << no_lref << std::endl << std::endl;
// 3. 测试右值引用
std::cout << "右值引用测试:\n";
int&& rref = 200; // 右值引用
remove_reference<int&&>::type no_rref = std::move(rref); // int&& → int
std::cout << "原始类型: " << typeid(rref).name()
<< ", 去除引用后: " << typeid(no_rref).name()
<< ", 值: " << no_rref << std::endl << std::endl;
// 4. 与标准库实现比较
std::cout << "与标准库(std::remove_reference)比较:\n";
using StdType = std::remove_reference<int&&>::type;
using OurType = remove_reference<int&&>::type;
if constexpr (std::is_same_v<StdType, OurType>) {
std::cout << "✅ 我们的实现与标准库一致: "
<< typeid(OurType).name() << std::endl;
} else {
std::cout << "❌ 实现不一致!" << std::endl;
}
// 5. 在函数签名中使用
std::cout << "\n函数签名应用示例:\n";
auto check_type = [](auto&& value) {
// 去除引用后获取基础类型
using BaseType = remove_reference<decltype(value)>::type;
if constexpr (std::is_integral_v<BaseType>) {
std::cout << "值: " << value
<< " (整数类型: " << typeid(BaseType).name() << ")\n";
} else if constexpr (std::is_floating_point_v<BaseType>) {
std::cout << "值: " << value
<< " (浮点类型: " << typeid(BaseType).name() << ")\n";
}
};
check_type(42); // 整数
int x = 100;
check_type(x); // 左值引用
check_type(std::move(x)); // 右值引用
check_type(3.14); // 浮点数
return 0;
}
特例化成员而不是类
特化成员函数而非整个类 - 完整示例说明
根据图片内容,下面实现一个模板类 Foo
,并只特化其 int
类型的成员函数 Bar
:
#include <iostream>
#include <string>
#include <typeinfo>
// 1. 原始模板类定义
template <typename T>
struct Foo {
Foo(const T &t = T()) : mem(t) {
std::cout << "构造通用Foo<" << typeid(T).name() << ">\n";
}
// 通用Bar函数
void Bar() {
std::cout << "通用Bar: " << mem << " (类型: " << typeid(T).name() << ")\n";
}
// 普通成员函数(不会被特化)
void Display() {
std::cout << "显示: " << mem << "\n";
}
T mem;
};
// 2. 只特化int类型的Bar成员函数
template<>
void Foo<int>::Bar() {
std::cout << "特例化Bar(int专用处理): "
<< mem << " (平方值: " << mem * mem << ")\n";
}
int main() {
std::cout << "=== 测试string类型 ===" << std::endl;
// 使用string类型
Foo<std::string> fs("Hello");
fs.Bar(); // 调用通用版本的Bar
fs.Display(); // 普通成员函数
std::cout << "\n=== 测试int类型 ===" << std::endl;
// 使用int类型
Foo<int> fi(5);
fi.Bar(); // 调用特例化版本的Bar
fi.Display(); // 普通成员函数(通用版本)
std::cout << "\n=== 测试double类型 ===" << std::endl;
// 使用double类型
Foo<double> fd(3.14);
fd.Bar(); // 调用通用版本的Bar
std::cout << "\n=== 验证默认构造 ===" << std::endl;
// 验证构造函数行为
Foo<int> defaultInt; // 调用通用构造函数(带默认值)
defaultInt.Bar(); // 调用特例化Bar
return 0;
}
输出结果示例:
=== 测试string类型 ===
构造通用Foo<St
通用Bar: Hello (类型: St)
显示: Hello
=== 测试int类型 ===
构造通用Foo<i
特例化Bar(int专用处理): 5 (平方值: 25)
显示: 5
=== 测试double类型 ===
构造通用Foo<d
通用Bar: 3.14 (类型: d)
=== 验证默认构造 ===
构造通用Foo<i
特例化Bar(int专用处理): 0 (平方值: 0)
关键概念解析:
部分特化:
- 只特化了
Bar
函数,没有特化整个Foo<int>
类 Display()
等其他成员函数仍然使用通用实现- 构造函数也保持通用实现(即使是对于
int
类型)
- 只特化了
语法结构:
template<> // 表示特例化 void Foo<int>::Bar() { // 指定特例化的是哪个类+成员 // 特殊处理逻辑 }
实例化规则:
Foo<string>
:所有成员函数都使用通用版本Foo<int>
:构造函数和Display()
使用通用版本,只有Bar()
使用特例化版本Foo<double>
:所有成员都使用通用版本(因为没有特化)