C++Primer shared_ptr类

静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存称为自由空间或者堆。程序用堆来储存动态分配的对象。那些在程序运行时分配的对象,动态对象的生存期由程序来控制,当动态对象不再使用时,我们必须进行显式地销毁它们。

为了更容易且更安全地使用动态内存,避免因new和delete使用不当而导致内存泄漏,新的标准库提供了两种智能指针来管理动态对象。智能指针类似常规指针,重要的区别是它负责自动释放所指向的对象。

新标准库提供智能指针的区别在于管理底层指针的方式
shared_ptr类:允许多个指针指向同一个对象
unique_ptr类:独占所指向的对象
weak_ptr类:弱引用,指向shared_ptr所管理的对象
三种类型都定义在memory头文件中

shared_ptr类
智能指针也是模板,当创建一个智能指针时,必须提供额外信息——指针可以指向的类型

shared_ptr<string> p1;   
shared_ptr<list<int>> p2;

默认初始化的智能指针中保存着一个空指针
解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

//shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp;   
unique_ptr<T> up;
p;    //将p作为一个判断条件,若p指向一个对象,则为ture
*p;   //获得P所指向的对象
p->mem;     //(*p).mem
p.get()     //返回p中保存的指针
swap(p, q); //交换p和q的指针
p.swap(q);
//shared_ptr独有的操作
make_shared<T>(args)   //返回一个shared_ptr,指向一个动态类型为T的对象,用args初始化该对象
shared_ptr<T> p(q)  //p是shared_ptr q的拷贝,此操作会递增q中的计数器,q中的指针必须能转成T*
p = q   //递增q的引用计数,递减p的引用计数,若p的引用计数变为0,则将其管理的原内存释放
p.unique()  //若p.use_count()为1,返回true;否则返回false
p.use_count()  //返回与p共享对象的智能指针数量

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

//p3指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
//p4指向一个值为“9999999999”的string
shared_ptr<string> p4 = make_shared<string>(10,'9');
//p5指向一个值初始化的int,即,值为0
shared_ptr<int> p5 = make_shared<int>();

make_shared<string>用其参数来构造给定类型的对象,如果我们不传递任何参数,对象就会进行值初始化。
我们通常用auto定义一个对象来保存make_shared的结果

//p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值
我们可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数(reference count),无论何时我们拷贝一个shared_ptr,计数器都会递增。当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或者是shared_ptr被销毁时(一个局部的shared_ptr离开其作用域时),计数器就会递减。

shared_ptr自动销毁所管理的对象
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr会自动销毁该对象,通过调用析构函数来实现。shared_ptr的析构函数会递减它指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占的内存。

shared_ptr还会自动释放相关联的内存

//factory返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<Foo> factory(T arg){
	//恰当处理arg
	//shared_ptr负责释放内存
	return make_shared<Foo>(arg);
}

由于factory返回一个shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。

void use_factory(T arg){
	shared_ptr<Foo> p = factory(arg);
	//使用p
}
//p离开作用域,它指向的内存就会被释放掉

当p被销毁时,将递减其引用计数并检查它是否为0。在此例中,p是唯一引用factory返回的内存的对象。由于p将要销毁,p指向的这个对象也会被销毁,所占有的内存会被释放。

//如果有其他shared_ptr指向这块内存,它就不会被释放掉
shared_ptr<Foo> use_factory(T arg){
	shared_ptr<Foo> p = factory(arg);
	//使用p
	return p;   //当返回p时,引用计数进行了递增的操作
}
//p离开了作用域,它指向的内存不会被释放掉

return语句为此函数的调用者返回了一个p的拷贝。拷贝一个shared_ptr会增加所管理对象的引用计数值。当p被销毁时,它指向的内存还有其他使用者。
对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被销毁掉。

使用了动态生存期的资源的类
1、程序不知道自己需要使用多少对象
2、程序不知道所需对象的准确类型
3、程序需要在多个对象之间共享数据

定义StrBlob类
定义一个管理string的类,命名为StrBlob,我们将使用vector来保存元素,为了防止当对象销毁时,vector中的数据也被销毁,我们将vector保存在动态内存中。
为了实现我们的数据共享,我们对于每一个StrBlob对象设置一个shared_ptr来管理动态分配的vector。此shared_ptr成员将记录有多少个StrBlob共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。

StrBlob的拷贝、赋值和销毁
我们的StrBlob类只有一个数据成员,它是shared_ptr类型。因此,当我们拷贝、赋值或者销毁一个StrBlob对象时,它的shared_ptr成员会被拷贝、赋值和销毁。
拷贝一个shared_ptr会递增其引用计数;将一个shared_ptr赋予另一个shared_ptr会递增赋值号右侧shared_ptr的引用计数,而递减左侧shared_ptr的引用计数。如果一个shared_ptr的引用计数变为0,它所指向的对象就会被自动销毁。对于StrBlob构造函数分配的vector,当最后一个指向它的StrBlob对象被销毁时,它会随之销毁。

编写你自己的StrBlob类,包含const版本的front和back

#include <iostream>
#include <initializer_list>
#include <vector>
#include <string>
#include <memory>
using namespace std;
class StrBlob {
public:
	typedef vector<string>::size_type size_type;
	StrBlob() : data(make_shared<vector<string>>()) {};  //默认构造函数分配一个空的vector
	//接受一个initializer_list的构造函数将其参数传递给对应的vector构造函数。此构造函数通过拷贝列表中的值来初始化vector中的元素
	StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {};  
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	//添加和删除元素
	void push_back(const string& t) { data->push_back(t); }
	void pop_back();
	//元素访问
	string& front();
	const string& front() const;
	string& back();
	const string& back() const;
private:
	shared_ptr<vector<string>> data;
	//如果data[i]不合法则抛出一个异常
	void check(size_type i, const string& msg) const {  //string描述了错误内容
		if (i >= data->size())
			throw out_of_range(msg);
	}
};
string& StrBlob::front() {
	//如果vector为空,check会抛出一个异常
	check(0, "front on empty StrBlob");
	return data->front();
}
const string& StrBlob::front() const{
	//如果vector为空,check会抛出一个异常
	check(0, "front on empty StrBlob");
	return data->front();
}
string& StrBlob::back() {
	check(0, "back on empty StrBlob");
	return data->back();
}
const string& StrBlob::back() const{
	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", "an", "the" };
		b1 = b2;
		b2.push_back("about");
		cout << b2.size() << endl;
	}
	cout << b1.size() << endl;
	cout << b1.front() << " " << b1.back() << endl;
	const StrBlob b3 = b1;
	cout << b3.front() << " " << b3.back() << endl;
	b1.pop_back();
	cout << b3.front() << " " << b3.back() << endl;
	b1.pop_back();
	b1.pop_back();
	cout << b1.front() << " " << b1.back() << endl;
	return 0;
}

在这里插入图片描述
在此代码的结尾,b1和b2包含多少个元素

StrBlob b1;
	{
		StrBlob b2 = { "a", "an", "the" };
		b1 = b2;
		b2.push_back("about");
		cout << b2.size() << endl;
	}

由于StrBlob的data成员是一个指向string的vector的shared_ptr,因此StrBlob的赋值不会拷贝vector的内容。而是多个StrBlob对象共享同一个创建在动态内存空间上的vector对象。
在代码结尾b1和b2中包含4个string

StrBlob需要const版本的push_back和pop_back吗
push_back和pop_back的语义分别是向StrBlob对象共享的vector对象添加和删除元素。我们不应该重载const版本,因为const StrBlob对象是不允许修改共享vector对象内容的

在check函数中,没有检查i是否大于0,为什么可以忽略这个检查
将check定义为私有成员函数,它只会被StrBlob的成员函数调用,而不会被用户程序所调用。因此,我们可以确保传递给i的值是符合要求的

未编写接受一个initializer_list explicit参数的构造函数,讨论这个设计的优缺点
未编写接受一个初始值列表参数的显式构造函数,意味着可以进行列表向StrBlob的隐式类型转化,在需要StrBlob的地方,可以使用列表进行替代。而且可以进行拷贝形式的初始化。令程序更为简单和方便。
这种隐式转化并不是都是好的。例如,列表中可能并非都是合法值。对于接受StrBlob的函数,传递给它一个列表,会创建一个临时的StrBlob对象,用列表对其进行初始化,然后将其传递给函数,当函数完成时,此对象被丢弃,再也无法访问。对于这种情况,我们可以定义显式的构造函数,禁止隐式类类型转换。

发布了69 篇原创文章 · 获赞 4 · 访问量 1219

猜你喜欢

转载自blog.csdn.net/CLZHIT/article/details/104041846