C++11特性

  • 右值引用与 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 版》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值