智能指针

一、简介

动态内存:除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间(free store)或堆(heap)。程序用堆来存储动态分配(dynamicallyallocate)的对象——即那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。

虽然使用动态内存有时是必要的,但正确地管理动态内存是非常棘手的。

智能指针存在的原因:动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时我们会忘记释放内存,在这种情况下就会产生内存泄漏;有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。

为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象。

  • 智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象
  • 这两种智能指针的区别在于管理底层指针的方式:
    • shared_ptr允许多个指针指向同一个对象;
    • unique_ptr则“独占”所指向的对象。
    • 标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
  • 这三种类型都定义在memory头文件中。

二、shared_ptr类

默认构造形式

类似vector,智能指针也采用模板类实现,shared_ptr对象的默认构造形式:shared_ptr<T> ptr;

shared_ptr<string> p1;
shared_ptr<list<int>> p2;
//默认初始化的智能指针中保存着一个空指针
用法

表12.1列出了shared_ptr和unique_ptr都支持的操作。只适用于shared_ptr的操作列于表12.2中。

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。make_shared也定义在头文件memory中。

当要用make_shared时,必须指定想要创建的对象的类型。定义方式与模板类相同:

//指向一个值为42的int的 shared_ptr
shared ptr<int> p3 = make_shared<int>(42),
//p4指向一个值为"99999999"的 string
shared ptr<string> p4 = make_shared<string>(10, '9')
//p5指向一个值初始化的int,即值为0
shared ptr<int> p5 = make_shared<int>

如果我们不传递任何参数,对象就会进行值初始化。我们通常用auto定义一个对象来保存make_shared的结果,这种方式较为简单:

//p6指向一个动态分配的空 vector< string>
auto p6 = make_shared<vector<string>>();
拷贝、复制、自动销毁

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时我们拷贝一个shared_ptr,计数器都会递增。

当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域时,计数器就会递减。)一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

auto r= make shared<int>(42);
r=q;
//我们分配了一个int,将其指针保存在r中。接下来,我们将一个新值赋予r。在此情况下,r是唯一指向此int的shared_ptr,在把q赋给r的过程中,此int被自动释放。

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数——析构函数(destructor)完成销毁工作的。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

/*《C++ primer》第十二章上的StrBlob类模板实现,我们希望其对象的不同拷贝之间共享相同的元素。
即当我们拷贝一个StrBlob时,原StrBlob对象及其拷贝应该引用相同的底层元素。*/
#include<iostream>
#include<vector>
#include<initializer_list>
#include<string>
#include<memory>
#include<stdexcept>
//using namespace std;

class StrBlob
{
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	void push_back(const std::string& t) { data->push_back(t); }
	void pop_back();
	std::string& front();
	std::string& back();
protected:
	int prot_mem;
private:
	std::shared_ptr<std::vector<std::string>> data;
	void check(size_type i, const std::string& msg) const;
};

StrBlob::StrBlob(std::initializer_list<std::string> il) :
	data(std::make_shared<std::vector<std::string>>(il))
{
}

void StrBlob::check(size_type i, const std::string& msg) const
{
	if (i >= data->size())
	{
		throw std::out_of_range(msg);
	}
}

std::string& StrBlob::front()
{
	check(0, "front on empty StrBlob");
	return data->front();
}

std::string& StrBlob::back()
{
	check(0, "back on empty StrBlob");
	return data->back();
}

void StrBlob::pop_back()
{
	check(0,"pop_back on empty StrBlob");
	data->pop_back();
}

int main()
{
	StrBlob b1;
	{
		StrBlob b2 = {"a","b","c"};
		b1 = b2;
		b2.push_back("d");
	}
	std::cout << b1.size() << std::endl;
	//std::cout << b2.size() << std::endl;
	return 0;
}

三、unique_ptr类

unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权,包括:

  1. 拥有它指向的对象
  2. 无法进行复制构造,无法进行复制赋值操作。即无法使两个unique_ptr指向同一个对象。但是可以进行移动构造和移动赋值操作
  3. 保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象

unique_ptr 可以实现如下功能:

  1. 为动态申请的内存提供异常安全
  2. 讲动态申请的内存所有权传递给某函数
  3. 从某个函数返回动态申请内存的所有权
  4. 在容器中保存指针
  5. auto_ptr 应该具有的功能
默认构造形式

类似vector,智能指针也采用模板类实现,unique_ptr对象的默认构造形式:unique_ptr<T> ptr;

unique_ptr<string> p1;
unique_ptr<list<int>> p2;
//默认初始化的智能指针中保存着一个空指针
用法

release与reset函数

虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

//将所有权从p1(指向 string Stegosaurus)转移给p2
unique_ptr< string>p2(p1. release());// release将p1置为空
unique ptr<string> p3(new string("Trex"));
//将所有权从p3转移给p2
p2.reset(p3.release());

reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原来指向的对象被释放。因此,对p2调用reset释放了用"Stegosaurus"初始化的string所使用的内存,将p3对指针的所有权转移给p2,并将p3置为空。调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。在本例中,管理内存的责任简单地从一个智能指针转移给另一个。

但是,如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:

p2.release()	//错误:p2不会释放内存,而且我们丢失了指针
auto p=p2.release();	//正确:但必须delete(p)
拷贝、复制

传递unique_ptr参数和返回unique_ptr不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。

posted @ 2021-02-05 17:56  东南亚季风  阅读(90)  评论(0编辑  收藏  举报