C++:C++Primer Plus(第六版):Chapter16 : string类和标准模板库:智能指针


本章节我们主要讲解 智能模板类,并主要从下面几个方面入手:

  1. 智能指针出现背景和现实需求
  2. 智能指针 使用范式
  3. 智能指针种类以及该如何选型

1.指针指针现实需求

智能指针是行为类似于指针的类对象,但这种对象还有其他功能,它可以管理给指针分配的对象

1.1 普通指针弊端

先看下面函数

#include<string>
void remodel(std::string& str)
{
	std::string* ps = new std::string(str);
	....
	std = ps;
	return;
}

上面的代码呢可以已经发现了这样一个现象:
该函数分配在堆中的随想,从来不收回,这会导致内存泄漏,你可能也知道解决之道: 在 return 前添加下面语句,释放内存即可

delete ps

但是这仍然会有问题,情况下面的变体。

#include<string>
void remodel(std::string & str)
{
	std::string *ps = new std::string(str);
	...
	if (weird_thing())
		throw exception();
	str = *ps;
	delete ps;
	return;
}

很显然,出现异常时,delete将不再执行,因此还是会导致内存泄漏。

1.2 智能指针现实需求

  1. 基于1.1章节,我们知道,你可能会在函数执行完毕,忘记 delete ps 或者在 delete ps 执行之前,代码发生异常。这两点都会无法释放内存,造成内存泄漏。
  2. 所以,我们需要有这样一种情况:函数栈在执行完毕后,指针ps 占的内存被释放,指针ps 指向的堆内存也会自动释放(这个可以通过析构函数释放)
  3. 但是 ps 只是一个常规指针,不是有析构函数的类对象,如果它是对象,那么可以在对象过期时,让它的析构函数删除指向的内存。
  4. 这就是 auto_ptr , unique_ptr和 shared_ptr 背后的思想所在。
  5. auto_ptr 是C++98 提供的解决方案,C++11 已经将摒弃,并提供了另外两种解决方案, 如果您的编译器不支持其他两种方案,那么 auto_ptr 是唯一的选择。

💚💚💚
下面来看一个使用智能指针的例子

在这里插入图片描述
很显然 auto_ptr 定义了类似指针的对象,可以将 new 获取(直接或间接)的地址赋给对象,当智能指针过期时,对象其析构函数 将使用 delete 来释放内存。

  1. 如果将 new返回的地址赋给这些对象,将无需记住稍后需要释放的内存。
  2. 智能指针在过期时,这些内存会自动释放。

2. 智能指针使用范式

  1. 要创建智能指针对象,必须包含头文件 memory, 下面是 auto_ptr 文件模板定义如下。
// 下面是伪代码
#include<memory>
#include<string>
template<class X>
class auto_ptr
{
public:
	eplicit auto_ptr(X* p = 0);
}

void create_auto_ptr()
{
	auto_ptr<double> pd(new double); // pd an auto_ptr to double
	auto_ptr<string> ps(new string);  // ps an auto_ptr string 
}
  1. new double/ new string 是new 返回的指针,指向新分配的内存块,它是构造函数 auto_ptr 的参数,对应原型中形参 p 的实参。

因此第一节的例子就可以这样转换
💚 包含头文件 memory
💚 指向 string 的指针 替换为 指向 string 的智能指针对象
💚 删除 delete语句

#include<string>
#include<memory>
void remodel(std::string & str)
{
	// std::string *ps = new std::string(str);
	std::auto_ptr<str::string> ps (new std::string(str));
	...
	if (weird_thing())
		throw exception();
	str = *ps;
	// delete ps;   no longer needed
	return;
}

2.1 下面演绎三种智能指针用法

#incdlue<iostream>
#include<string>
#include<memory>
class Report
{
private:
    std::string str;
public:
	Report(const std::string s):str(s)
	{
		std::cout<< "Objcet create \n";
	}
	~Report()
	{
		std::cout << "Object deleted\n";
    }
	void comment() const
	{
		std::cout <<str << std::endl;
    }
}

int main()
{
    {
    	// 将 new Report 对象地址 赋值给 auto_ptr 构造函数 实参p
		std::auto_ptr<Report> ps(new Report("using auto_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	
	{
    	// 将 new Report 对象地址 赋值给 shared_ptr 构造函数 实参p
		std::shared_ptr<Report> ps(new Report("using shared_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	{
    	// 将 new Report 对象地址 赋值给 unique_ptr 构造函数 实参p
		std::unique_ptr<Report> ps(new Report("using unique_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	return 0;
}

// 打印结果
Objcet create 
using auto_ptr
Object deleted

Objcet create 
using shared_ptr
Object deleted

Objcet create 
using unique_ptr
Object deleted
  1. 所有智能指针类都有一个 explicit 构造函数,该构造函数将指针作为参数,因此不需要自动将指针转换为 智能指针对象,更不要将指针转换成智能指针。如
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg;  // not allow (implicit conversion)
pd = shared_ptr<double>(p_reg);  // allow (explicit conversion)

shared_ptr<double> pshared = p_reg;  // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg);  // allowed (explicit conversion)

注意一个问题

下面这个问题,全部三种智能指针都应该避免

string vacation("I wandeed lonely as a child");
shared_ptr<string> pvac(&vacation);  // error 

// pvac 过期时,程序将把 delete 运算符用于非 堆内存,这是 错误的

3. 三种智能指针特点

3.1 为何摒弃 auto_ptr

先看下面语句

string str = new string("I wandeed lonely as a child");
string *ps = &str;
string* vocation;
vocation = ps;  // 将指针赋值给另一个指针
delete ps;
delete vocation;

💚 上面这段语句会完成什么操作了 ?

  1. 这两个指针都指向一个string 对象,这是不能接受的,因为程序将视图删除同一个对象 两次,一次是 ps 过期时,另一次是 vocation过期时,要避免这种问题方法有很多 。
    🧡
  2. 定义赋值运算符:使之执行深度赋值,这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本。
  3. 建立所有权(ownership) 概念:对于特定对象,只能有一个指针拥有它,这样只有拥有对象的指针指针的析构函数才可以删除,然后,让赋值操作转让所有权,这就是 auto_ptr 和unique_ptr 的策略,但是 unique_ptr 的策略更严格
  4. 创建智能更高的指针,跟踪引用特定对象的智能指针,这被称为引用计数 (reference counting)。

🚀 下面我们来通过另一个例子详细说明:为何摒弃 auto_ptr。

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	auto_ptr<string> film[5]=
	{
		
		auto_ptr<string> (new string("Flowl Bals")),
		auto_ptr<string> (new string("Duck walks")),
		auto_ptr<string> (new string("Chicken run")),
		auto_ptr<string> (new string("Turkey errors")),
		auto_ptr<string> (new string("Goose Eggs"))
	};
	
	auto_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}

// 打印结果
Flowl Bals
Duck walks
Segmentation fault

很显然,这是由于错误的使用 auto_ptr 可能导致的问题(但是这种行为是不确定的,其行为可能随系统而定),这里的问题是由于下面的语句将 :所有权 从 films[2] 转让给 pwin

pwin = films[2] // films[2] loses ownership
这导致 films[2] 不再引用 该字符串,在 auto_ptr 放弃对象所有权后,你又通过 *films[2] 访问指向的对象,却发现这是一个空指针。
这便是 auto_ptr 令人讨厌的地方。

3.2 选用 unique_ptr

如果我们将 auto_ptr 修改成 unipue_ptr 了 ?

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	unique_ptr<string> film[5]=
	{
		
		unique_ptr<string> (new string("Flowl Bals")),
		unique_ptr<string> (new string("Duck walks")),
		unique_ptr<string> (new string("Chicken run")),
		unique_ptr<string> (new string("Turkey errors")),
		unique_ptr<string> (new string("Goose Eggs"))
	};
	
	unique_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}

好家伙,直接报错,都不能编译通过,

💚💚💚这便是 unique_str 与 auto_ptr 的区别

  1. 它避免了 films[2] 指针不再指向有效数据的问题,即悬挂指针问题。
    在这里插入图片描述

3.3 选用 shared_ptr

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	shared_ptr<string> film[5]=
	{
		
		shared_ptr<string> (new string("Flowl Bals")),
		shared_ptr<string> (new string("Duck walks")),
		shared_ptr<string> (new string("Chicken run")),
		shared_ptr<string> (new string("Turkey errors")),
		shared_ptr<string> (new string("Goose Eggs"))
	};
	
	shared_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}
// 打印结果
Flowl Bals
Duck walks
Chicken run
Turkey errors
Goose Eggs
the winner is: Chicken run

这次 pwin和films[2] 指向同一个对象。

  1. 引用计数从 1 增加到 2
  2. 在程序结尾,后声明的 pwin先调用析构函数,该析构函数将引用计数降低到1
  3. 对 films[2] 调用析构函数时,引用计数降低到0 ,这个时候就可以释放以前分配的空间。

4. 应该使用哪种智能指针

  1. 如果程序使用多个指向同一个对象的指针,应选择 shared_ptr
  2. STL容器包含指针,很多STL算法都支持 复制和赋值操作,这些操作可用于 shared_ptr
  3. 如果您的编译器没有体用 shared_ptr 可以使用 Boost库提供的 shared_ptr
  4. 如果程序不需要多个指向同一个对象的指针,则可以使用 unique_ptr 。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值