文章目录
-
- C++11新特性详解(三):高级特性与实用工具
- 一、可变参数模板
-
- [1.1 为什么需要可变参数模板](#1.1 为什么需要可变参数模板)
- [1.2 可变参数模板的基本语法](#1.2 可变参数模板的基本语法)
- [1.3 参数包的展开](#1.3 参数包的展开)
- [1.4 emplace系列接口的实现](#1.4 emplace系列接口的实现)
- 二、lambda表达式
-
- [2.1 lambda表达式的起源](#2.1 lambda表达式的起源)
- [2.2 lambda表达式的语法](#2.2 lambda表达式的语法)
- [2.3 捕获列表详解](#2.3 捕获列表详解)
- [2.4 lambda的实际应用](#2.4 lambda的实际应用)
- [2.5 lambda的底层原理](#2.5 lambda的底层原理)
- 三、function包装器
-
- [3.1 为什么需要function](#3.1 为什么需要function)
- [3.2 function的基本使用](#3.2 function的基本使用)
- [3.3 function的实际应用场景](#3.3 function的实际应用场景)
- 四、bind绑定器
-
- [4.1 bind的作用](#4.1 bind的作用)
- [4.2 bind的基本用法](#4.2 bind的基本用法)
- [4.3 bind的实际应用](#4.3 bind的实际应用)
- [4.4 bind vs lambda](#4.4 bind vs lambda)
- 五、其他实用特性
-
- [5.1 范围for循环](#5.1 范围for循环)
- [5.2 auto关键字](#5.2 auto关键字)
- [5.3 decltype类型推导](#5.3 decltype类型推导)
- [5.4 nullptr](#5.4 nullptr)
- 六、总结与展望
-
- [6.1 C++11全系列回顾](#6.1 C++11全系列回顾)
- [6.2 C++11的影响](#6.2 C++11的影响)
- [6.3 后续版本预览](#6.3 后续版本预览)
- 七、最佳实践建议
-
- [7.1 代码风格建议](#7.1 代码风格建议)
- [7.2 性能优化建议](#7.2 性能优化建议)
- [7.3 常见陷阱](#7.3 常见陷阱)
C++11新特性详解(三):高级特性与实用工具
💬 欢迎讨论:本文是C++11新特性系列的完结篇,我们将学习可变参数模板、lambda表达式、函数包装器等高级特性。这些特性让C++代码更加灵活和优雅!
👍 点赞、收藏与分享:如果这个系列对你有帮助,记得三连支持!也欢迎分享给更多学习C++的朋友!
🚀 系列回顾:在前两篇中,我们学习了列表初始化、类功能增强、右值引用和移动语义。本篇将介绍更多实用的高级特性。
一、可变参数模板
1.1 为什么需要可变参数模板
在C++98中,如果我们想实现一个支持任意数量参数的函数模板,需要写很多重载版本:
cpp
// C++98的做法:需要写多个重载版本
template<class T>
void print(const T& t)
{
cout << t << endl;
}
template<class T1, class T2>
void print(const T1& t1, const T2& t2)
{
cout << t1 << " " << t2 << endl;
}
template<class T1, class T2, class T3>
void print(const T1& t1, const T2& t2, const T3& t3)
{
cout << t1 << " " << t2 << " " << t3 << endl;
}
// 还需要更多版本...
这样的代码不仅冗长,而且有参数数量的限制。C++11引入了可变参数模板,让我们能够用一个模板处理任意数量的参数。
1.2 可变参数模板的基本语法
声明可变参数模板
cpp
// Args是一个模板参数包
template<class... Args>
void func(Args... args)
{
// args是一个函数参数包
}
关键概念
class... Args:声明一个模板参数包,Args代表0个或多个类型Args... args:声明一个函数参数包,args代表0个或多个参数sizeof...(Args):获取参数包中参数的个数
基本示例
cpp
#include <iostream>
using namespace std;
// 打印参数包中参数的个数
template<class... Args>
void ShowList(Args... args)
{
cout << "参数个数: " << sizeof...(args) << endl;
}
int main()
{
ShowList(); // 参数个数: 0
ShowList(1); // 参数个数: 1
ShowList(1, 'A'); // 参数个数: 2
ShowList(1, 'A', string("hello")); // 参数个数: 3
return 0;
}
1.3 参数包的展开
仅仅知道参数包的大小是不够的,我们需要访问包中的每个参数。C++提供了几种展开参数包的方式。
方式一:递归函数方式展开
这是最直观的方式,类似于递归处理:
cpp
// 递归终止函数(没有参数时调用)
void ShowList()
{
cout << endl;
}
// 递归展开参数包
template<class T, class... Args>
void ShowList(const T& val, Args... args)
{
cout << val << " ";
ShowList(args...); // 递归调用,处理剩余参数
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', string("hello"));
return 0;
}
输出
bash
1
1 A
1 A hello
展开过程分析
以ShowList(1, 'A', "hello")为例,展开过程如下:
cpp
// 第1次调用
ShowList(1, 'A', "hello")
cout << 1 << " ";
ShowList('A', "hello"); // 递归
// 第2次调用
ShowList('A', "hello")
cout << 'A' << " ";
ShowList("hello"); // 递归
// 第3次调用
ShowList("hello")
cout << "hello" << " ";
ShowList(); // 递归
// 第4次调用(终止函数)
ShowList()
cout << endl;
方式二:逗号表达式展开
利用逗号表达式和初始化列表,可以在一个表达式中展开参数包:
cpp
template<class T>
void PrintArg(const T& t)
{
cout << t << " ";
}
template<class... Args>
void ShowList(Args... args)
{
int arr[] = {(PrintArg(args), 0)...};
cout << endl;
}
int main()
{
ShowList(1, 'A', string("hello"), 3.14);
return 0;
}
展开过程分析
cpp
// 逗号表达式:(PrintArg(args), 0)
// 展开为:
int arr[] = {
(PrintArg(1), 0),
(PrintArg('A'), 0),
(PrintArg(string("hello")), 0),
(PrintArg(3.14), 0)
};
每个逗号表达式先执行PrintArg(args),然后取值0,最终数组被初始化为全0。
1.4 emplace系列接口的实现
可变参数模板最典型的应用就是STL容器的emplace系列接口。这些接口可以直接在容器内部构造对象,避免临时对象的创建。
为什么需要emplace
先看传统的push_back:
cpp
vector<pair<int, string>> vec;
// 方式1:构造临时对象,然后拷贝/移动
vec.push_back(pair<int, string>(1, "hello"));
// 方式2:使用make_pair,但仍有临时对象
vec.push_back(make_pair(1, "hello"));
这两种方式都会创建临时对象,然后拷贝或移动到容器中。
emplace_back的优势
cpp
// 直接在容器内部构造对象,没有临时对象
vec.emplace_back(1, "hello");
emplace_back直接将参数转发给元素的构造函数,在容器的内存空间中就地构造对象。
实现emplace_back
cpp
template<class T>
class vector
{
public:
// 使用可变参数模板和完美转发
template<class... Args>
void emplace_back(Args&&... args)
{
// 检查是否需要扩容
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
// 在_finish位置就地构造对象
new (_finish) T(std::forward<Args>(args)...);
++_finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
// 移动旧元素
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = std::move(_start[i]);
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
size_t size() const { return _finish - _start; }
size_t capacity() const { return _endofstorage - _start; }
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endofstorage = nullptr;
};
关键点分析
- 可变参数模板 :
template<class... Args>接受任意数量、任意类型的参数 - 完美转发 :
std::forward<Args>(args)...保持参数的值类别 - placement new :
new (_finish) T(...)在指定位置构造对象 - 参数包展开 :
forward<Args>(args)...将所有参数转发给构造函数
性能对比测试
cpp
#include <vector>
#include <iostream>
#include <chrono>
using namespace std;
class BigObject
{
public:
BigObject(int x, int y) : _x(x), _y(y)
{
cout << "BigObject(int, int)" << endl;
}
BigObject(const BigObject& obj)
: _x(obj._x), _y(obj._y)
{
cout << "BigObject(const BigObject&)" << endl;
}
BigObject(BigObject&& obj)
: _x(obj._x), _y(obj._y)
{
cout << "BigObject(BigObject&&)" << endl;
}
private:
int _x;
int _y;
};
int main()
{
vector<BigObject> v;
cout << "===== push_back =====" << endl;
v.push_back(BigObject(1, 2));
cout << "\n===== emplace_back =====" << endl;
v.emplace_back(3, 4);
return 0;
}
输出
bash
===== push_back =====
BigObject(int, int)
BigObject(BigObject&&)
===== emplace_back =====
BigObject(int, int)
可以看到,emplace_back只调用了一次构造函数,而push_back还额外调用了移动构造。
二、lambda表达式
2.1 lambda表达式的起源
在C++98中,如果我们想给算法传递一个简单的谓词函数,通常需要定义一个函数对象或全局函数:
cpp
// C++98的做法:需要定义函数对象
struct Greater
{
bool operator()(int a, int b)
{
return a > b;
}
};
int main()
{
vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
sort(v.begin(), v.end(), Greater());
return 0;
}
这样的代码冗长且不直观。C++11引入了lambda表达式,让我们可以在使用的地方直接定义匿名函数。
2.2 lambda表达式的语法
基本语法
cpp
[capture](parameters) mutable -> return_type { body }
[capture]:捕获列表,指定如何捕获外部变量(parameters):参数列表,和普通函数一样mutable:可选,允许修改按值捕获的变量-> return_type:可选,返回类型(通常可以自动推导){ body }:函数体
最简单的lambda
cpp
int main()
{
// 最简单的lambda:无捕获,无参数,无返回值
auto f = []() {
cout << "Hello Lambda!" << endl;
};
f(); // 调用lambda
// 带参数的lambda
auto add = [](int a, int b) {
return a + b;
};
cout << add(3, 5) << endl; // 输出8
// 显式指定返回类型
auto divide = [](double a, double b) -> double {
if (b == 0) return 0;
return a / b;
};
return 0;
}
2.3 捕获列表详解
捕获列表是lambda表达式最强大的特性,它决定了lambda如何访问外部变量。
捕获方式
| 捕获形式 | 说明 |
|---|---|
[] |
不捕获任何变量 |
[=] |
按值捕获所有外部变量 |
[&] |
按引用捕获所有外部变量 |
[x] |
按值捕获变量x |
[&x] |
按引用捕获变量x |
[=, &x] |
按值捕获所有变量,但x按引用捕获 |
[&, x] |
按引用捕获所有变量,但x按值捕获 |
[this] |
捕获this指针 |
示例代码
cpp
int main()
{
int a = 1, b = 2, c = 3;
// 按值捕获a和b
auto f1 = [a, b]() {
cout << a << " " << b << endl;
// a = 10; // 错误!按值捕获的变量是const的
};
// 按引用捕获a和b
auto f2 = [&a, &b]() {
a = 10; // OK,可以修改
b = 20;
cout << a << " " << b << endl;
};
// 按值捕获所有变量
auto f3 = [=]() {
cout << a << " " << b << " " << c << endl;
};
// 按引用捕获所有变量
auto f4 = [&]() {
a = 100;
b = 200;
c = 300;
};
// 混合捕获:默认按值,c按引用
auto f5 = [=, &c]() {
cout << a << " " << b << endl;
c = 1000; // 可以修改c
};
f1(); // 输出:1 2
f2(); // 输出:10 20,并修改了a和b
f3(); // 输出:10 20 3
f4(); // 修改a、b、c
f5(); // 输出:100 200,修改c
cout << "a=" << a << " b=" << b << " c=" << c << endl;
// 输出:a=100 b=200 c=1000
return 0;
}
mutable关键字
按值捕获的变量默认是const的,如果想修改,需要加mutable关键字:
cpp
int main()
{
int x = 10;
// 使用mutable允许修改按值捕获的变量
auto f = [x]() mutable {
x = 20; // 修改的是lambda内部的拷贝
cout << "lambda内部: " << x << endl;
};
f();
cout << "外部: " << x << endl;
return 0;
}
输出
bash
lambda内部: 20
外部: 10
注意:mutable修改的只是lambda内部的拷贝,不影响外部变量。
2.4 lambda的实际应用
在STL算法中使用
cpp
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
// 排序:降序
sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
// 查找:第一个大于5的元素
auto it = find_if(v.begin(), v.end(), [](int x) {
return x > 5;
});
if (it != v.end())
cout << "找到: " << *it << endl;
// 计数:大于等于5的元素个数
int count = count_if(v.begin(), v.end(), [](int x) {
return x >= 5;
});
cout << "大于等于5的元素有: " << count << "个" << endl;
// 遍历:打印所有元素
for_each(v.begin(), v.end(), [](int x) {
cout << x << " ";
});
cout << endl;
return 0;
}
作为回调函数
cpp
class Button
{
public:
void setClickHandler(function<void()> handler)
{
_clickHandler = handler;
}
void click()
{
if (_clickHandler)
_clickHandler();
}
private:
function<void()> _clickHandler;
};
int main()
{
Button btn;
int clickCount = 0;
// 设置点击事件处理函数
btn.setClickHandler([&clickCount]() {
++clickCount;
cout << "按钮被点击了 " << clickCount << " 次" << endl;
});
btn.click(); // 按钮被点击了 1 次
btn.click(); // 按钮被点击了 2 次
btn.click(); // 按钮被点击了 3 次
return 0;
}
2.5 lambda的底层原理
lambda表达式本质上是一个函数对象 。编译器会为每个lambda生成一个匿名的类,并重载operator()。
编译器的转换
cpp
// 我们写的lambda
int a = 10;
auto f = [a](int x) {
return x + a;
};
编译器大致转换为:
cpp
class Lambda_xxxx
{
public:
Lambda_xxxx(int a) : _a(a) {}
int operator()(int x) const
{
return x + _a;
}
private:
int _a; // 捕获的变量成为成员变量
};
Lambda_xxxx f(a);
验证
cpp
int main()
{
int a = 1, b = 2;
// 每一个 lambda 表达式都会生成一个唯一的闭包类型
auto f1 = [a, b](int x) { return x + a + b; };
auto f2 = [a, b](int x) { return x + a + b; };
// 虽然捕获列表、参数和函数体完全相同
// 但 f1 和 f2 来自不同的 lambda 表达式,因此类型不同
// f1 = f2; // ❌ 错误:不同的闭包类型,不能赋值
return 0;
}
三、function包装器
3.1 为什么需要function
在C++中,可调用对象有很多种形式:
cpp
// 1. 普通函数
int add(int a, int b)
{
return a + b;
}
// 2. 函数指针
int (*pFunc)(int, int) = add;
// 3. 函数对象(仿函数)
struct Add
{
int operator()(int a, int b)
{
return a + b;
}
};
// 4. lambda表达式
auto lambda = [](int a, int b) {
return a + b;
};
// 5. 成员函数
class Math
{
public:
int add(int a, int b) { return a + b; }
static int sub(int a, int b) { return a - b; }
};
这些可调用对象的类型都不相同,很难统一处理。std::function就是用来统一这些可调用对象的包装器。
3.2 function的基本使用
包装各种可调用对象
cpp
#include <functional>
#include <iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
struct Multiply
{
int operator()(int a, int b)
{
return a * b;
}
};
class Math
{
public:
int sub(int a, int b)
{
return a - b;
}
static int divide(int a, int b)
{
return a / b;
}
};
int main()
{
// 包装普通函数
function<int(int, int)> f1 = add;
cout << f1(10, 5) << endl; // 15
// 包装函数对象
function<int(int, int)> f2 = Multiply();
cout << f2(10, 5) << endl; // 50
// 包装lambda
function<int(int, int)> f3 = [](int a, int b) {
return a - b;
};
cout << f3(10, 5) << endl; // 5
// 包装静态成员函数
function<int(int, int)> f4 = Math::divide;
cout << f4(10, 5) << endl; // 2
// 包装成员函数(需要额外的对象参数)
function<int(Math*, int, int)> f5 = &Math::sub;
Math m;
cout << f5(&m, 10, 5) << endl; // 5
return 0;
}
function作为函数参数
cpp
void execute(function<int(int, int)> func, int a, int b)
{
cout << "结果: " << func(a, b) << endl;
}
int main()
{
execute(add, 10, 5);
execute([](int a, int b) { return a * b; }, 10, 5);
execute(Multiply(), 10, 5);
return 0;
}
3.3 function的实际应用场景
场景一:实现回调机制
cpp
class HttpClient
{
public:
using Callback = function<void(const string&)>;
void get(const string& url, Callback onSuccess, Callback onError)
{
// 模拟HTTP请求
if (url.empty())
{
onError("URL is empty");
}
else
{
onSuccess("Response from " + url);
}
}
};
int main()
{
HttpClient client;
client.get("https://example.com",
[](const string& response) {
cout << "成功: " << response << endl;
},
[](const string& error) {
cout << "失败: " << error << endl;
}
);
return 0;
}
场景二:实现观察者模式
cpp
class Subject
{
public:
using Observer = function<void(const string&)>;
void attach(Observer observer)
{
_observers.push_back(observer);
}
void notify(const string& message)
{
for (auto& observer : _observers)
{
observer(message);
}
}
private:
vector<Observer> _observers;
};
int main()
{
Subject subject;
// 注册多个观察者
subject.attach([](const string& msg) {
cout << "观察者1收到: " << msg << endl;
});
subject.attach([](const string& msg) {
cout << "观察者2收到: " << msg << endl;
});
// 通知所有观察者
subject.notify("Hello Observers!");
return 0;
}
场景三:实现命令模式
cpp
class Editor
{
public:
using Command = function<void()>;
void execute(Command cmd)
{
cmd();
_history.push(cmd);
}
void undo()
{
if (!_history.empty())
{
_history.pop();
}
}
private:
stack<Command> _history;
};
四、bind绑定器
4.1 bind的作用
std::bind可以将可调用对象与其参数绑定在一起,生成一个新的可调用对象。主要用途:
- 调整参数顺序
- 绑定部分参数(偏函数)
- 绑定成员函数
4.2 bind的基本用法
绑定普通函数
cpp
#include <functional>
#include <iostream>
using namespace std;
int add(int a, int b, int c)
{
return a + b + c;
}
int main()
{
// 绑定所有参数
auto f1 = bind(add, 1, 2, 3);
cout << f1() << endl; // 6
// 使用占位符:_1表示第一个参数,_2表示第二个参数
using namespace placeholders;
auto f2 = bind(add, _1, _2, 3);
cout << f2(10, 20) << endl; // 33
// 调整参数顺序
auto f3 = bind(add, _2, _1, 3);
cout << f3(10, 20) << endl; // 33 (20 + 10 + 3)
// 参数复用
auto f4 = bind(add, _1, _1, _1);
cout << f4(5) << endl; // 15 (5 + 5 + 5)
return 0;
}
绑定成员函数
cpp
class Math
{
public:
int add(int a, int b)
{
cout << "Math::add called" << endl;
return a + b;
}
};
int main()
{
Math m;
// 绑定成员函数(第一个参数必须是对象)
auto f = bind(&Math::add, &m, placeholders::_1, placeholders::_2);
cout << f(10, 20) << endl; // 30
// 也可以绑定部分参数
auto f2 = bind(&Math::add, &m, 100, placeholders::_1);
cout << f2(50) << endl; // 150
return 0;
}
4.3 bind的实际应用
场景一:适配器模式
cpp
// 假设我们有一个只接受一个参数的函数接口
void processNumber(function<int(int)> func, int x)
{
cout << func(x) << endl;
}
int multiply(int a, int b)
{
return a * b;
}
int main()
{
// 使用bind将两参数函数适配为一参数函数
auto f = bind(multiply, placeholders::_1, 5);
processNumber(f, 10); // 50
return 0;
}
场景二:延迟执行
cpp
class TaskQueue
{
public:
void addTask(function<void()> task)
{
_tasks.push_back(task);
}
void executeTasks()
{
for (auto& task : _tasks)
{
task();
}
_tasks.clear();
}
private:
vector<function<void()>> _tasks;
};
void process(const string& name, int value)
{
cout << name << ": " << value << endl;
}
int main()
{
TaskQueue queue;
// 使用bind绑定参数,稍后执行
queue.addTask(bind(process, "Task1", 100));
queue.addTask(bind(process, "Task2", 200));
queue.addTask(bind(process, "Task3", 300));
cout << "开始执行任务..." << endl;
queue.executeTasks();
return 0;
}
输出
bash
开始执行任务...
Task1: 100
Task2: 200
Task3: 300
4.4 bind vs lambda
在很多场景下,bind和lambda可以互相替代,但各有优势:
使用bind
cpp
auto f1 = bind(add, placeholders::_1, placeholders::_2, 10);
使用lambda
cpp
auto f2 = [](int a, int b) {
return add(a, b, 10);
};
对比
| 特性 | bind | lambda |
|---|---|---|
| 可读性 | 较差(需要理解占位符) | 更好(代码更直观) |
| 灵活性 | 适合简单的参数绑定 | 可以包含复杂逻辑 |
| 性能 | 可能有额外开销 | 通常更高效 |
| 推荐度 | C++11可用 | C++11及以后推荐优先使用 |
现代C++的建议
在C++11及以后,除非有特殊需求,通常推荐使用lambda而不是bind,因为lambda更直观、更灵活。
五、其他实用特性
5.1 范围for循环
C++11引入了更简洁的遍历容器的语法:
cpp
int main()
{
vector<int> v = {1, 2, 3, 4, 5};
// C++98的写法
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
// C++11的范围for循环
for (int x : v)
{
cout << x << " ";
}
cout << endl;
// 使用引用修改元素
for (int& x : v)
{
x *= 2;
}
// 使用auto自动推导类型
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
return 0;
}
范围for的底层原理
范围for循环本质上是一个语法糖,编译器会将其转换为迭代器的形式:
cpp
// 我们写的代码
for (auto x : v)
{
cout << x << endl;
}
// 编译器转换后
for (auto it = v.begin(); it != v.end(); ++it)
{
auto x = *it;
cout << x << endl;
}
5.2 auto关键字
C++11的auto关键字可以让编译器自动推导变量类型:
cpp
int main()
{
// 自动推导基本类型
auto x = 10; // int
auto y = 3.14; // double
auto str = "hello"; // const char*
// 自动推导复杂类型
vector<int> v;
auto it = v.begin(); // vector<int>::iterator
// 自动推导lambda类型
auto lambda = [](int x) { return x * 2; };
// 自动推导函数返回类型
auto result = add(10, 20);
return 0;
}
auto的使用场景
- 简化复杂类型的声明
cpp
// 不使用auto
map<string, vector<int>>::iterator it = mymap.begin();
// 使用auto
auto it = mymap.begin();
- 与范围for配合
cpp
for (auto& pair : mymap)
{
cout << pair.first << ": " << pair.second << endl;
}
- 无法手动写出的类型
cpp
auto lambda = [](int x) { return x * 2; };
// lambda的真实类型无法手动写出
auto的注意事项
cpp
int x = 10;
auto a = x; // int,拷贝
auto& b = x; // int&,引用
const auto& c = x; // const int&,const引用
auto* p = &x; // int*,指针
// auto会丢失const和引用
const int y = 20;
auto d = y; // int,不是const int
// d = 30; // OK,d不是const
// 保持const需要显式指定
const auto e = y; // const int
// e = 30; // 错误!
5.3 decltype类型推导
decltype用于推导表达式的类型,但不会计算表达式的值:
cpp
int main()
{
int x = 10;
decltype(x) y = 20; // y的类型是int
const int& rx = x;
decltype(rx) ry = x; // ry的类型是const int&
// 推导函数返回类型
decltype(add(1, 2)) result; // result的类型是int
// 推导lambda类型
auto lambda = [](int x) { return x * 2; };
decltype(lambda) another_lambda = lambda;
return 0;
}
auto vs decltype
| 特性 | auto | decltype |
|---|---|---|
| 用途 | 变量声明时推导类型 | 推导表达式的类型 |
| const/引用 | 会丢失 | 会保留 |
| 是否计算表达式 | 是 | 否 |
decltype的典型应用:泛型编程
cpp
template<class T1, class T2>
auto add(T1 a, T2 b) -> decltype(a + b) // 返回类型后置
{
return a + b;
}
// C++14可以简化为
template<class T1, class T2>
auto add(T1 a, T2 b)
{
return a + b; // 自动推导返回类型
}
5.4 nullptr
C++11引入nullptr来替代NULL,解决了NULL的二义性问题:
cpp
void func(int x)
{
cout << "func(int)" << endl;
}
void func(int* p)
{
cout << "func(int*)" << endl;
}
int main()
{
// C++98的问题
func(NULL); // 可能调用func(int),产生歧义
// C++11的解决方案
func(nullptr); // 明确调用func(int*)
return 0;
}
nullptr的优势
- 类型安全:
nullptr是std::nullptr_t类型,不会与整数混淆 - 更好的重载决议:能够正确匹配指针类型的重载
- 语义清晰:明确表示空指针
六、总结与展望
6.1 C++11全系列回顾
通过这三篇文章,我们系统学习了C++11的核心特性:
第一篇:基础特性
- 列表初始化:统一的初始化语法
initializer_list:支持任意数量参数的容器初始化- 成员变量就地初始化
=default和=delete:精确控制默认函数
第二篇:右值引用与移动语义
- 左值与右值的本质区别
- 右值引用的引入
- 移动构造和移动赋值
- 完美转发与引用折叠
- 解决传值返回问题
第三篇:高级特性
- 可变参数模板:编译期递归
- lambda表达式:匿名函数
function包装器:统一可调用对象bind绑定器:参数绑定auto、decltype、nullptr等实用特性
6.2 C++11的影响
C++11对C++的发展产生了深远影响:
编程范式的变革
- 函数式编程:lambda表达式让函数式编程风格成为可能
- 现代资源管理:移动语义提供了更高效的资源管理方式
- 泛型编程增强:可变参数模板大大提升了模板的表达能力
代码质量的提升
- 更简洁:范围for、auto、lambda等特性减少了冗余代码
- 更安全:
nullptr、类型推导等特性减少了错误 - 更高效:移动语义、右值引用等特性提升了性能
标准库的现代化
- 智能指针:
unique_ptr、shared_ptr - 线程库:
thread、mutex、condition_variable - 正则表达式:
regex - 随机数:
random - 时间库:
chrono
6.3 后续版本预览
C++11之后,C++标准每3年更新一次:
C++14(2014)
- 泛型lambda
- 返回类型推导
- 变量模板
make_unique
C++17(2017)
- 结构化绑定
if和switch中的初始化语句std::optional、std::variant- 并行算法
C++20(2020)
- 概念(Concepts)
- 协程(Coroutines)
- 模块(Modules)
- 范围库(Ranges)
- 三路比较运算符
C++23(2023)
std::printstd::expected- 更多的范围适配器
七、最佳实践建议
7.1 代码风格建议
优先使用现代C++特性
cpp
// 不推荐:C++98风格
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
{
cout << *it << endl;
}
// 推荐:C++11风格
for (auto x : vec)
{
cout << x << endl;
}
合理使用auto
cpp
// 好的使用
auto it = map.find(key); // 迭代器类型很长
auto lambda = [](int x) { return x * 2; }; // lambda类型无法写出
// 不好的使用
auto x = 5; // 类型不明显,降低可读性
// 建议:int x = 5;
优先使用lambda而不是bind
cpp
// 不推荐
auto f = bind(add, _1, 10);
// 推荐
auto f = [](int x) { return add(x, 10); };
7.2 性能优化建议
使用移动语义
cpp
// 返回大对象时,依赖编译器优化和移动语义
vector<int> createVector()
{
vector<int> v(1000000);
// ...
return v; // 不要用std::move,编译器会自动优化
}
使用emplace而不是push/insert
cpp
// 不推荐
vec.push_back(make_pair(key, value));
// 推荐
vec.emplace_back(key, value);
避免不必要的拷贝
cpp
// 不好
for (auto x : vec) // 拷贝每个元素
{
// ...
}
// 好
for (const auto& x : vec) // 引用,避免拷贝
{
// ...
}
7.3 常见陷阱
陷阱1:lambda捕获的生命周期
cpp
function<int()> getFunc()
{
int x = 10;
return [&x]() { return x; }; // 危险!x的生命周期已结束
}
陷阱2:移动后使用对象
cpp
string s = "hello";
auto s2 = std::move(s);
cout << s << endl; // 危险!s已被移动,处于未定义状态
陷阱3:范围for中修改容器
cpp
for (auto x : vec)
{
vec.push_back(x); // 危险!迭代过程中修改容器可能导致迭代器失效
}
至此,C++11新特性系列全部完成!从基础特性到高级特性,从移动语义到lambda表达式,我们系统地学习了C++11带来的革命性变化。这些特性不仅让C++代码更加现代化,也为后续版本的发展奠定了基础。希望这个系列能够帮助你更好地掌握现代C++!
C++11新特性系列到此圆满结束!感谢你的阅读,如果对你有帮助,记得点赞、收藏、分享!期待在评论区看到你的学习心得!❤️
