现代c++编程c++11/14/17/20:Chapter 05 Smart Pointers and Memory Management

5.1 RAII and Reference Counting

理解Objective-C/Swift/JavaScript的程序员应该知道引用计数的概念。对引用计数进行计数以防止内存泄漏。基本思想是计算动态分配对象的数量。每当添加对同一对象的引用时,所引用对象的引用计数将增加一次。每删除一个引用,引用计数就减少1。当对象的引用计数减少到零时,所指向的堆内存将被自动删除。

在传统的c++中,记住手动释放资源并不总是最佳实践。因为我们很容易忘记释放资源,导致泄漏。所以通常的做法是,对于对象,我们在构造函数时分配space,在析构函数(在离开作用域时调用)时释放space。也就是说,我们常说RAII资源获取是初始化技术。

凡事都有例外,我们总是需要在空闲存储上分配对象。在传统的c++中,我们必须使用new和delete来记住释放资源。c++ 11引入了智能指针的概念,使用了引用计数的思想,因此程序员不再需要关心手动释放内存的问题。这些智能指针包括std::shared_ptr/std::unique_ptr/std::weak_ptr,它需要包含头文件。

注意:引用计数不是垃圾收集。引用计数可以尽快恢复不再使用的对象,并且在回收过程中不会造成长时间的等待。更清晰、更清晰地表明资源的生命周期。

5.2 std::shared_ptr

std::shared_ptr是一个智能指针,它记录shared_ptr指向一个对象的指针数,排除调用delete,当引用计数变为0时,它会自动删除该对象。
但是这还不够,因为使用std::shared_ptr仍然需要用new来调用,这使得代码在一定程度上不对称。
std::make_shared可用于消除new的显式使用,因此std::make_shared将在生成的参数中分配对象。并返回这个对象类型的std::shared_ptr指针。例如:

#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i)
{
	(*i)++;
}
int main()
{
	// auto pointer = new int(10); // illegal, no direct assignment
	// Constructed a std::shared_ptr
	auto pointer = std::make_shared<int>(10);
	foo(pointer);
	std::cout << *pointer << std::endl; // 11
	// The shared_ptr will be destructed before leaving the scope
	return 0;
}

std::shared_ptr可以通过get()方法获得原始指针,并通过reset()减少引用计数。并通过use_count()查看一个对象的引用计数。例如:

auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // reference count+1
auto pointer3 = pointer; // reference count+1
int *p = pointer.get(); // no increase of reference count
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();

std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0,pointer2 has reset

std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();

std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0,pointer3 has reset

5.3 unique_ptr

unique_ptr是一个exclusive智能指针,它禁止其他智能指针共享同一个对象,从而保持代码的安全:unique_ptr是一个exclusive智能指针,它禁止其他智能指针共享同一个对象,从而保持代码的安全:

std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique was introduced in C++14
std::unique_ptr<int> pointer2 = pointer; // illegal

make_unique并不复杂。c++ 11不提供std::make_unique,它可以自己实现:

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
	return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

至于为什么没有提供它,c++标准委员会主席Herb Sutter在他的博客中提到,那是因为它们被遗忘了.

因为它是垄断的,换句话说,它不能被复制。然而,我们可以使用std::move将它传输到其他unique_ptr对象,例如:

#include <iostream>
#include <memory>
struct Foo {
	Foo() { std::cout << "Foo::Foo" << std::endl; }
	~Foo() { std::cout << "Foo::~Foo" << std::endl; }
	void foo() { std::cout << "Foo::foo" << std::endl; }
};
void f(const Foo &) {
	std::cout << "f(const Foo&)" << std::endl;
}

int main() {
	std::unique_ptr<Foo> p1(std::make_unique<Foo>());
	// p1 is not empty, prints
	if (p1) p1->foo();
	
	{
		std::unique_ptr<Foo> p2(std::move(p1));

		// p2 is not empty, prints
		f(*p2);
		// p2 is not empty, prints
		if(p2) p2->foo();
		// p1 is empty, no prints
		if(p1) p1->foo();
		p1 = std::move(p2);
		// p2 is empty, no prints
		if(p2) p2->foo();
		std::cout << "p2 was destroyed" << std::endl;
	}
	// p1 is not empty, prints
	if (p1) p1->foo();

	// Foo instance will be destroyed when leaving the scope
}

5.4 std::weak_ptr

如果仔细考虑std::shared_ptr,您仍然会发现仍然存在一个无法释放资源的问题。看看下面的例子:

在这里插入图片描述
结果是A和B不会被破坏。这是因为a, b中的指针也引用了a, b,这使得a, b的引用计数变成了2,离开了作用域。当a, b智能指针被解构时,它只能导致该区域的引用计数减少1。这导致a, b对象指向的内存区域引用计数非零,但外部无法找到该区域,这也导致了内存泄漏,如图5.1所示.

解决这个问题的方法是使用弱引用指针std::weak_ptr,它是一个弱引用(与std::shared_ptr相比,std::shared_ptr是一个强引用)。弱引用不会导致引用计数的增加。当使用弱引用时,最终的发布过程显示在图5.2:

在这里插入图片描述
在上图中,最后一步只剩下B, B没有任何智能指针来引用它,所以这个内存资源也将被释放。std::weak_ptr没有*操作符和->操作符,所以它不能对资源进行操作。它唯一的功能就是检查std::shared_ptr是否存在,它的expired()方法可以在资源未被释放时返回true,否则返回false。

5.5 Conclusion

智能指针的技术并不新颖。它是许多语言中的通用技术。现代c++引入了这种技术,在一定程度上消除了new/delete的滥用。它是一项比较成熟的技术。编程范式。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值