详细介绍:C++11/14 高速上手教程
一、右值引用与std::move
如下代码:
std::string tmp("bert")
std::string name(tmp)
第一行声明了一个对象tmp,用"bert"字符串进行初始化
第二行声明了一个对象name,使用参数tmp调用string的构造函数,因此对象name复制了"bert"的一个副本,如下:
如下代码:
std::string tmp(“bert”);
std::string name(std::move(tmp));
直接将tmp中的内容移动到name中,而不是复制一份,用图表示为:
二、智能指针
-
unique_ptr
#include <string.h> #include <stdio.h> #include <memory> #include <iostream> using namespace std; class Student { public: Student() { cout << "Construct student" << endl; } ~Student() { cout << "Destruct student" << endl; } bool Register() { return true; } bool Enroll() { return true; } }; int main() { Student* p = new Student(); if (!p->Register()) { delete p; return -1; } if (!p->Enroll()) { delete p; return -2; } delete p; return 0; }
该代码使用 new 在堆上分配一个 Student 对象,并对其调用了两个成员函数:Register 和 Enroll。任意一个失败都将返回失败,并且不能忘记 delete 资源。在三处 return 语句之前,必须加上 delete p;否则就可能造成资源泄漏;如果使用 unique_ptr,就可以在指针 p 退出作用域时,自动释放所分配的资源,完整代码如下:
#include <string.h> #include <stdio.h> #include <memory> #include <iostream> using namespace std; class Student { public: Student() { cout << "Construct student" << endl; } ~Student() { cout << "Destruct student" << endl; } bool Register(Student* p) { return true; } bool Enroll(Student* p) { return true; } }; // 编译 g++ -std=c++11 -o unique_ptr_test unique_ptr_test.cc int main() { unique_ptr<Student> p(new Student()); if (!p->Register()) { // 不需要任何操作,p指向的资源会自动释放 return -1; } if (!p->Enroll()) { // 不需要任何操作,p指向的资源会自动释放 return -2; } // 不需要任何操作,p指向的资源会自动释放 return 0; }
利用 unique_ptr,无须操心释放资源,避免内存泄漏。
另外,unique_ptr 也适用于消除“内部分配,外部释放”这种易错的机制,比如 c 语言的 strdup 函数:
int main() { // strdup函数内部为字符串分配内存,并返回其地址 char* p = strdup("bert"); // ...使用p // 使用者必须释放strdup分配的内存,否则内存泄漏 free(p); return 0; }
可以看到 strdup 函数的使用,给使用者打开了犯错的窗户,一不小心就会内存泄漏。而使用 unique_ptr 则可以解决这一问题:
std::unique_ptr<char []> my_strdup(const char* s) { if (s == nullptr) return std::unique_ptr<char []>(nullptr); //计算字符串长度 size_t len = strlen(s); //用智能指针管理分配的内存 std::unique_ptr<char []> str(new char[len+1]); //拷贝字符串s strcpy(str.get(), s); //返回分配的新字符串 return str; } int main() { auto p = my_strdup("bert"); // ... // 使用p // 使用者什么都不需要做,无须担心内存泄漏 return 0; }
unique_ptr 是旧标准库中 auto_ptr 的升级版,由于独占性语义,它不允许被复制,但是可以 move:
std::unique_ptr p(new Student()); std::unique_ptr p2(std::move(p));
所以,unique_ptr 是可以被放到 STL 容器中;旧版本要求容器中的元素必须可以被复制,而现在放宽了:movable 的对象也是可以放入容器的:
std::vector<std::unique_ptr<int>> ptr_vec; ptr_vec.push_back(std::unique_ptr<int>(new int(123)));
-
shared_ptr
shared-ptr 是功能非常强大的智能指针,和 unique_ptr 不同的是,它是基于引用计数的,可以被多个所有者共同持有;当最后一个持有者退出作用域时,资源自动释放;它完美解决了资源释放时机的问题,试想一下多个裸指针指向同一个资源时,释放资源将是多么头疼和难以正确,甚至需要使用观察者模式来帮助使用者清理指针。而shared-ptr 完美解决了这一问题,完整代码如下:
#include <string.h> #include <stdio.h> #include <memory> #include <thread> #include <iostream> using namespace std; typedef int Resourse; shared_ptr<Resourse> CreateResourse() { return make_shared<Resourse>(1); } void User1(shared_ptr<Resourse> p) { if (p) { //1 使用p cout << "use p in user1 thread." << endl; } return; } void User2(shared_ptr<Resourse> p) { this_thread::sleep_for(chrono::milliseconds(10)); if (p) { //2 使用p cout << "use p in user2 thread." << endl; } return; } int main() { shared_ptr<Resourse> res = CreateResourse(); // 启动线程t1和t2,运行user函数,使用res thread t1(User1, res); thread t2(User2, res); // 等待线程结束,谁也不需要考虑res资源的释放 t1.join(); t2.join(); return 0; }
shared_ptr 类似于 java 中的引用:多个引用指向一个对象,只有最后一个引用失效时候,该对象才可以被垃圾回收。
-
weak_ptr
weak_ptr 是配合 shared_ptr 存在,主要是为了解决两个问题:一是循环引用问题,使得资源无法释放;
例如 A 对象含有一个shared_ptr<B>
,而 B 对象也含有一个shared_ptr<A>
,那么很容易产生循环引用,使得内存无法释放。weak_ptr 要解决的另外一个问题是臭名昭著的悬挂指针(dangling pointer):指针指向的内存被删除;一个简单的场景是,A 线程创建资源,并传递给 B 线程,B 线程只读访问资源;但是 A 线程随后可能释放了资源,B 没有感知,而得到了一个悬挂指针。#include <string.h> #include <stdio.h> #include <memory> #include <thread> #include <iostream> using namespace std; typedef int Resourse; shared_ptr<Resourse> g_resourse; void thread_a() { // 2. 创建全局资源 g_resourse = make_shared<Resourse>(1); // 3. 睡眠3秒钟 this_thread::sleep_for(chrono::seconds(3)); // 6. 释放资源 g_resourse = nullptr; cout << "free resourse, thread A exit." << endl; } void thread_b() { // 1. 休眠,让线程A先创建资源 this_thread::sleep_for(chrono::milliseconds(100)); // 4. 创建weak_ptr访问资源,它可以有效检测出悬挂指针: weak_ptr<Resourse> pw(g_resourse); // 5. 隔一秒钟访问资源,若资源被释放了,则退出线程; int i = 0; while (1) { i++; // 调用weak_ptr的lock()尝试提升到shared_ptr auto res(pw.lock()); if (res) { // 在6之前: 提升成功,指针res有效,可以使用资源,然后睡眠1秒钟 cout << i << ":Success read resourse from thread B." << endl; this_thread::sleep_for(chrono::seconds(1)); } else { cout << "Fail read resourse from thread B, exit." << endl; return; // 7. 说明资源被释放了,出现了"悬挂指针"情况,线程退出 } } } int main() { //启动线程A std::thread t_a(thread_a); //启动线程B std::thread t_b(thread_b); // 请注意看线程代码注释中的序号,大致代表了代码的执行顺序 //等待线程结束 t_a.join(); t_b.join(); return 0; }
输出:
1:Success read resourse from thread B. 2:Success read resourse from thread B. 3:Success read resourse from thread B. free resourse, thread A exit. Fail read resourse from thread B, exit.
首先 A 线程释放了资源并退出,然后 B 线程使用 weak_ptr 感知到了资源释放,避免了出现悬挂指针错误
三、lambda,bind,function
C++11引入了lambda,它极大的方便了程序员编写临时函数,甚至可以模拟闭包。而bind可以适配函数签名,及其灵活。
#include <string.h>
#include <stdio.h>
#include <memory>
#include <functional>
#include <iostream>
using namespace std;
int main() {
int r = 0;
// lambda,是一个可执行对象,类型是void (int* )
auto updateLambda = [](int* res) { (*res)++; };
// 将lambda赋值给callback,后者类型是 void (), 由于签名不符,需要bind做适配
std::function<void ()> callback = std::bind(updateLambda, &r);
// 执行callback
callback();
// 现在,r应该递增为1
cout << "From main: r = " << r << endl;
return 0;
}
输出
From main: r = 1
使用 bind 将 void (int*)类型的 lambda,适配到类型为 void ()的 function
四、语法糖:auto,foreach
以前声明一个迭代器:
map<int, int> mm;
map<int, int>::const_iterator it(mm.begin());
现在只需要:
auto it(mm.begin());
以前遍历 map:
for (map<int, int>::const_iterator it(mm.begin()); it != mm.end(); ++it) {
cout << it->first << “ -> ” << it->second << endl;
}
现在:
for (const auto& kv : mm) {
cout << kv.first << “ -> ” << kv.second << endl;
}