C++模板教程:类型推导与完美转发
本文摘要
完美转发是保留参数左右值属性// 接收函数:区分左值右值cout << "左值处理: " << value << endl;cout << "右值处理: " << value << endl;//…
本文是 C++ 系列教程的第 19 篇。上一篇讲解了变参模板与 SFINAE,本篇深入现代 C++ 的核心机制:auto 推导规则、decltype/decltype(auto)、左值右值与移动语义、右值引用与引用折叠、std::move、std::forward、完美转发实战。
一、auto 类型推导
1.1 auto 的基本用法
C++11 引入 auto,让编译器自动推导类型:
#include <iostream>#include <vector>#include <map>using namespace std;
int main() { auto i = 42; // int auto d = 3.14; // double auto s = string("hi"); // string auto p = &i; // int*
cout << i << " " << d << " " << s << endl;
// 简化复杂类型 map<string, vector<int>> data; // 旧写法:map<string, vector<int>>::iterator it = data.begin(); auto it = data.begin(); // 自动推导迭代器类型
// 范围 for 中常用 vector<int> v = {1, 2, 3}; for (auto &x : v) x *= 2; // auto& 推导为 int& return 0;}1.2 auto 的推导规则
#include <iostream>using namespace std;
int main() { int x = 42; int &ref = x; const int cx = 10;
// 推导规则 1:auto 会忽略顶层 const 和引用 auto a1 = x; // int(拷贝) auto a2 = ref; // int(引用被忽略,拷贝) auto a3 = cx; // int(const 被忽略)
// 推导规则 2:auto& 保留引用和 const auto &a4 = ref; // int& const auto &a5 = cx; // const int&
// 推导规则 3:auto* 保留指针 auto *a6 = &x; // int*
// 修改测试 a2 = 100; cout << "x = " << x << endl; // 42(a2 是拷贝,不影响 x) a4 = 200; cout << "x = " << x << endl; // 200(a4 是引用) return 0;}1.3 auto 的使用注意
#include <iostream>#include <vector>using namespace std;
int main() { // 注意:auto 用于函数返回类型时丢失 const/引用 // 需要时用 auto& 或 decltype(auto)
// 初始化列表不能直接推导 // auto list = {1, 2, 3}; // C++17 推导为 initializer_list<int>
// 空初始化不允许 // auto e; // 错误!无法推导
// 不要在局部用 auto 声明意图不明的代码 // auto useless = someFunction(); // 可读性差
vector<int> v = {1, 2, 3}; // 只读遍历用 const auto&,避免拷贝 for (const auto &x : v) { // x 是 const int& } return 0;}二、decltype 类型推导
2.1 decltype 的作用
decltype 推导表达式的精确类型(保留 const、引用):
#include <iostream>using namespace std;
int main() { int x = 42; const int cx = 10; int &ref = x;
// decltype 保留精确类型 decltype(x) d1; // int decltype(cx) d2 = 10; // const int decltype(ref) d3 = x; // int&
// 表达式结果 decltype(x + 1) d4; // int decltype(x * 2.5) d5; // double decltype(cout << x) d6 = cout; // ostream&
// 变量 + 括号的区别 decltype(x) d7; // int(变量) decltype((x)) d8 = x; // int&(带括号是左值表达式) return 0;}2.2 auto vs decltype 对比
| 维度 | auto | decltype |
|---|---|---|
| 方式 | 值推导 | 表达式推导 |
| const | 忽略顶层 const | 保留 |
| 引用 | 忽略 | 保留 |
| 用途 | 简化声明 | 精确类型 |
2.3 返回类型推导
#include <iostream>#include <type_traits>using namespace std;
// C++11:尾置返回类型template <typename T, typename U>auto add11(T a, U b) -> decltype(a + b) { return a + b;}
// C++14:auto 自动推导template <typename T, typename U>auto add14(T a, U b) { return a + b;}
// C++14:decltype(auto) 保留精确类型template <typename T>decltype(auto) identity(T &&value) { return forward<T>(value); // 完美转发}
int main() { cout << add11(3, 4.5) << endl; // 7.5 cout << add14(2.5, 3) << endl; // 5.5
int x = 10; decltype(auto) ref = identity(x); // int&(保留引用) ref = 99; cout << "x = " << x << endl; // 99 return 0;}三、左值与右值
3.1 左值右值概念
- 左值(lvalue):有名字、可取地址、可持久存在的表达式。
- 右值(rvalue):临时对象、无名字、即将销毁的表达式。
#include <iostream>using namespace std;
int main() { int x = 42; // x 是左值 int y = x + 1; // x+1 是右值(临时结果) // &(x + 1); // 错误!不能对右值取地址 // &42; // 错误!字面量是右值
string s1 = "hello"; // s1 是左值 string s2 = s1 + "!"; // s1+"!" 是右值(临时对象)
cout << &x << endl; // 可以对左值取地址 // cout << &(s1 + "!") << endl; // 错误 return 0;}3.2 右值引用 &&
右值引用可以绑定到右值9.“接章”临时对象的资源:
#include <iostream>#include <string>using namespace std;
int main() { // 左值引用 & 只绑定左值 int x = 42; int &lref = x; // OK:左值引用绑定左值 // int &lref2 = 42; // 错误:不能绑定右值
// 右值引用 && 只绑定右值 int &&rref = 42; // OK:不能绑定字格的值 // int &&rref2 = x; // 错误:不能监定左值
// 但右值引用可以免合 move 绑定左值 int &&rref3 = move(x); // move 把会家 佬换为右值
string &&sref = string("temp"); // 绑定临时字符串 cout << sref << endl; // temp return 0;}3.3 std::move 的作用
std::move 只是把左值转换为右值引用(cast),以便调用移动构造函数:
#include <iostream>#include <string>#include <utility>using namespace std;
class Buffer {private: string data;public: // 拷贝构造(深拷贝) Buffer(const string &s) : data(s)ˆ��]����-'N��]H[�ˆB���9���b�9��:`(;�"9��y�z-a9��;�"B��Y��\���[�� ���H�]J[ݙJ�JHˆ��]����b���]H[�ˆB�N‚�[�XZ[�Hˆ��[���HH�[��ܛ�ˆ�Y��\��J�JN���9���-'y��:`(;�"�H9�+�m�`/;�"B��Y��\���[ݙJ�JJN���9���b�9��:`(;�"[ݙH:/k9c��`/;�"B���[ݙH9d#��H9⭹� y�*�k��.b{�#:`&�n.9..��n���]\��ŸB�����9f��� yo%y�*9��9c�������H9o%y�*9��9c�:)�9b&B��od��(y�o�cl9��9k�9.���'��#9o%y�*9�9o%y�*8�#y����#9�"z)�9b&y��9c�;�&���9��9k�9h�9�#�9��9c�9������KKHKKHKKH�HI� ��I��HI� �I��HI�� ��I���HI�� �I�����)�9b&J���&�c�)�y�"y. 9.*��+�m�`/9o%y�*;�#9�����9l,y�+�m�`/9o%y�*;�&�c깧"y.)9.*�`�y�+�c��`/9o%y�*9�cy�+�c��`/9o%y�*8� ��������:/k9c�yo%y�*;�"9.!� �yo%y�*;�"B�����[��YH[���X[O��\�[���[Y\�X�H�‚��� ��9g*9�(y�o�.+y.#y�+�c��`/9o%y�*;�#: #9�+Ⱥ/k9c�yo%y�*��"9.!� �yo%y�*;�"B�[\]H\[�[YH����Y�X�� ���[YJHˆY�\���[YWܙY�\�[��Wݏ�Hˆ��]�9�+�m�`/9o%y�*�[�ˆH[�Hˆ��]�9�+�c��`/9o%y�*�[�ˆB�B��[�XZ[�Hˆ[�H�ˆ�X��N���9/(9aiym�`/8���H[� �8��� ��9�#�b�9..�[� ���X���N���9/(9aiyc��`/8���H[�8��� ��9l,y�+�[� ����]\��ŸB�����9.�8� yk�9/k9c�H����ܝ�\������K�H9.�9.b9�+�k�9���/k9c�B�����`"y.��.��k��. 9cl9g*:+���%�c����9."�� 9n���#9c�y�"y. 9.*�cl9�c9�+;�&������[��YH[���X[O���[��YH][]O��\�[���[Y\�X�H�‚���9��y�/�a�y�l;�&�ab9�'�`/9m�`/���Y���\��[� ��[YJHˆ��]���m�`/9i!9�!�WH��[YH[�ŸB����Y���\��[� ���[YJHˆ��]���c��`/9i!9�!�WH��[YH[�ŸB����:/k9c�ya�y�l;�&���9���/�zem�m�`/9c����9c�y/fylg�.��c��`/�[\]H\[�[YH����Y�ܝ�\�\� ���[YJHˆ���ܝ�\��9/�zem�b���"ylg�.��c��`/:`&��B����\���ܝ�\���[YJJNŸB����:e&z+���*9o#��&��$y.�y���/gy/#y�.�`/9`��.��m�`/�[\]H\[�[YH����Y�Y�ܝ�\�\� ���[YJHˆ���\���[YJN����[YH9�+9.���+�m�`/8���9n,��+�m�`/�B��[�XZ[�Hˆ[�H�ˆ�ܝ�\�\�N�����m�`/9i!9�!�WH��":/k9c�y/�yei9m�`/;�"B��ܝ�\�\�LN�����c��`/9i!9�!�WHL;�":/k9c�y/�zem�c��`/;�"B���Y�ܝ�\�\�LN�����m�`/9i!9�!�WHL;�":e&z+��� y.c�.��c��`/9lg��)��"B��\�]\��ŸB������K���ܝ�\���[ݙH9k�y����9��9n�����[ݙH����ܝ�\��KKHKKHKKH�9/g9�*9��9�hy.��.��/�9���#9c��/k9�h�c��`/9�"y�hy.��/k9c�{�#9/�y�iyc��`/�9�*9��H[ݙJH�ܝ�\��H�:"�y��z`"y��H9���b�:-a9��9k�9/k9c�{�"B�9k���9.$�.&��fy. 9��9�cyb,9.�9.&�`/9/�y�i9c��`/
int x = 42; check(x); // 传入左值 → T = int& → T&& 折叠为 int& check(42); // 传入右值 → T = int → T&& 就是 int&& return 0;}五、完美转发 std::forward
5.1 什么是完美转发
完美转发是保留参数左右值属性地传递给下一个函数:
#include <iostream>#include <utility>using namespace std;
// 接收函数:区分左值右值void process(int &value) { cout << "左值处理: " << value << endl;}
void process(int &&value) { cout << "右值处理: " << value << endl;}
// 转发函数:完美转发保留左右值属性template <typename T>void forwarder(T &&value) { // forward<T> 保留原始属性 process(forward<T>(value));}
// 错误示范:直接传递会变成左值template <typename T>void badForwarder(T &&value) { process(value); // value 本身是左值 → 总是左值处理}
int main() { int x = 42; forwarder(x); // 左值处理: 42(转发保留左值) forwarder(100); // 右值处理: 100(转发保留右值)
badForwarder(100); // 左值处理: 100(错误!丢了右值属性) return 0;}5.2 forward vs move 对比
| 维度 | std::move | std::forward |
|---|---|---|
| 作用 | 无条件转右值 | 有条件转发 |
| 用法 | move(x) | forward(x) |
| 场景 | 移动资源 | 完美转发 |
| 语义 | 不管原来是什么 | 保留原来属性 |
5.3 完美转发实战:make_unique 简化版
#include <iostream>#include <utility>using namespace std;
template <typename T>class MyUniquePtr {private: T *ptr;
public: // 构造:接收任意参数,完美转发给 T 的构造函数 template <typename... Args> explicit MyUniquePtr(Args &&... args) : ptr(new T(forward<Args>(args)...)) { cout << "创建对象" << endl; }
~MyUniquePtr() { delete ptr; cout << "销毁对象" << endl; }
T *get() const { return ptr; }};
class Person {private: string name; int age;public: Person(string n, int a) : name(move(n)), age(a) {} void show() const { cout << name << " " << age << "岁" << endl; }};
// 模拟 make_uniquetemplate <typename T, typename... Args>MyUniquePtr<T> myMakeUnique(Args &&... args) { return MyUniquePtr<T>(forward<Args>(args)...);}
int main() { // ��美转发参数到 Person 构造函数 auto p = myMakeUnique<Person>("张三", 25); p.get()->show(); // 张三 25岁 return 0;}六、移动语义实战
6.1 移动 vs 拷贝性能对比
#include <iostream>#include <vector>#include <string>using namespace std;
int main() { // 深拷贝 vs 移动 vector<string> v1; for (int i = 0; i < 1000; i++) { string s(10000, 'x');
// v1.push_back(s); // 拷贝:分配新内存复制 v1.push_back(move(s)); // 移动:接管 s 的内存 } cout << "移动构造完成,v1 大小: " << v1.size() << endl;
// 返回值优化 / 移动返回 auto makeVector = []() { vector<int> temp(1000, 7); return temp; // 返回局部对象(移动或 RVO) }; auto v2 = makeVector(); cout << "v2 大小: " << v2.size() << endl; return 0;}6.2 移动构造与移动赋值
#include <iostream>#include <cstring>using namespace std;
class DynamicBuffer {private: char *data; size_t length;
public: // 构造 DynamicBuffer(size_t len) : data(new char[len]), length(len) { cout << "构造 " << length << " 字节" << endl; }
// 拷贝构造(深拷贝) DynamicBuffer(const DynamicBuffer &other) : data(new char[other.length]), length(other.length) { memcpy(data, other.data, length); cout << "拷贝构造" << endl; }
// 移动构造(接管资源) DynamicBuffer(DynamicBuffer &&other) noexcept : data(other.data), length(other.length) { other.data = nullptr; // 置空源对象 other.length = 0; cout << "移动构造" << endl; }
// 移动赋值 DynamicBuffer &operator=(DynamicBuffer &&other) noexcept { if (this == &other) return *this; delete[] data; data = other.data; length = other.length; other.data = nullptr; other.length = 0; cout << "移动赋值" << endl; return *this; }
~DynamicBuffer() { delete[] data; }};
DynamicBuffer createBuffer() { return DynamicBuffer(1000); // 临时对象(右值)}
int main() { DynamicBuffer b1 = createBuffer(); // 移动构造 DynamicBuffer b2(500); // 普通构造 b2 = createBuffer(); // 移动赋值 return 0;}七、实战:通用容器包装
综合本篇知识,实现支持完美转发的容器:
#include <iostream>#include <memory>#include <utility>using namespace std;
// 通用包装器:支持任意类型任意参数构造template <typename T>class Wrapper {private: unique_ptr<T> impl;
public: // 完美转发构造 template <typename... Args> explicit Wrapper(Args &&... args) : impl(make_unique<T>(forward<Args>(args)...)) {}
// 移动构造 Wrapper(Wrapper &&) = default; Wrapper &operator=(Wrapper &&) = default;
// 拷贝删除(独占) Wrapper(const Wrapper &) = delete; Wrapper &operator=(const Wrapper &) = delete;
T *operator->() const { return impl.get(); } T &operator*() const { return *impl; }};
class Config {private: string host; int port; vector<string> features;
public: Config(string h, int p, initializer_list<string> fs) : host(move(h)), port(p), features(fs) {}
void show() const { cout << host << ":" << port << " 功能: "; for (const auto &f : features) cout << f << " "; cout << endl; }};
int main() { // 完美转发多个参数 Wrapper<Config> config("localhost", 8080, {"http", "https", "ws"}); config->show(); // localhost:8080 功能: http https ws return 0;}总结
本篇讲解了 auto 推导规则与注意点、decltype/decltype(auto) 的精确类型、左值右值的概念与右值引用、引用折叠规则、std::move 与 std::forward 的区别、完美转发的实现,并用通用容器包装串联实战。重点掌握:auto 忽略 const/引用而 decltype 保留、引用折叠规则、forward 保留左右值属性、移动语义避免深拷贝。
模板阶段(16-19 篇)完成!下一篇进入现代 C++ 特性精讲:移动语义与智能指针实战,敬请期待!
C++模板教程:类型推导与完美转发
作者:Wang Shijie
本文链接:https://wsj-blog.pages.dev/posts/c-mu-ban-jiao-cheng-lei-xing-tui-dao-yu-wan-mei-zhuan-fa/
本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
留言评论
评论资源加载失败,请重试。