-
右值引用与 std::move
某些场景使用 rvalue ref 有更高的性能; 右值可以简单理解为”即将作废的对象”std::string tmp(“bert”); std::string name(tmp);
赋值过去之后,tmp如果不再用,就会有浪费,拷贝”bert”字符串似乎是个浪费。所以在 cpp11 中引入了右值引用,解决这一问题:
std::string tmp(“bert”); std::string name(std::move(tmp)); // 注意区别:std::move // 现在,tmp已经处于一种无效状态,你不应该使用tmp了
可以看到,move 操作将原本属于 tmp 的字符串”bert”,转移给了 name 对 象;现在,tmp 对象内部的字符串指针指向了 NULL,所以 tmp 不应该再被访问使用了。
但并不是任何场合都要用 move。现代编译器足够的聪明,特别是能够执行 RVO 优化的地方,不要加 std::move。有兴趣的可以自行搜索返回值优化,Effective c++书籍也有讲解。
// 代码示例 std::string MakeName(const std::string& prefix) { std::string name(prefix); name += ".txt"; return name; // RVO优化 //return std::move(name); 如果你画蛇添足使用了这行代码,反而防止了编译器优化, //带来一次move constructor的开销 } // 由于RVO优化,函数内局部变量name直接在n上构建。 std::string n = MakeName("hello");
-
智能指针
智能指针的出现,使得 c++ 成为一种“自带 gc”的语言。只要掌握好三种智能指针,并合理运用 STL 容器替代数组,想出内存问题都难。 -
unique_ptr
它表示对资源的一种独占,运用 C++ RAII 机制,实现了作用域范围的内存控制。//no_unique_ptr.cc #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; }
如果使用 unique_ptr,我们就可以在指针 p 退出作用域时,自动释放所分配的资源
//unique_ptr_test.cc #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 则可以解决这一问题:
// 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)); // 现在p2指向了Student, p则成为了null指针。
所以,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 完美解决了这一问题,完整代码如下:
// shared_ptr_test.cc #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; } 实践:使用命令 `g++ -o shared_ptr_test -std=c++11 shared_ptr_test.cc -lpthread ` 得到可执行文件。
-
weak_ptr
weak_ptr 是配合 shared_ptr 存在,主要是为了解决两个问题:一是循环引用问题,使得资源无法释放;例如 A 对象含有一个shared_ptr<B>
,而 B 对象也含有一个shared_ptr<A>
,那么很容易产生循环引用,使得内存无法释放。本文不详细阐述这个问题,有兴趣可以参考《c++标准库第 2 版》的 5.2 节。
weak_ptr 要解决的另外一个问题是臭名昭著的悬挂指针(dangling pointer):指针指向的内存被删除;一个简单的场景是,A 线程创建资源,并传递给 B 线程,B 线程只读访问资源;但是 A 线程随后可能释放了资源,B 没有感知,而得到了一个悬挂指针。// weak_ptr_test.cc #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; } g++ -o weak_test -std=c++11 weak_ptr_test.cc -lpthread
-
lambda,bind,function
c++11 引入了 lambda,它极大的方便了程序员编写临时函数,甚至可以模拟闭包。而 bind 可以适配函数签名,极其灵活。下面这个例子展示了如何使用 bind 将签名不匹配的 lambda 和 function 适配起来//lambda_test.cc #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; } g++ -o lambda_test -std=c++11 lambda_test.cc
使用 bind 将 void (int*)类型的 lambda,适配到类型为 void ()的 function ; Lambda 和 bind 在实际运用中较为灵活。
-
语法糖:auto,foreach
C++11 加入了一些语法糖,使得写代码更为便利。例如以前声明一个迭代器: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; }
现在使用 foreach 遍历:
for (const auto& kv : mm) { cout << kv.first << “ -> ” << kv.second << endl; }
有关 C++11 的一本好书是《Effective modern c++》;另外一本优秀参考书是《C++标准库第 2 版》。
C++11特性
最新推荐文章于 2021-12-03 23:42:30 发布