C++智能指针

智能指针是行为类似于指针的类对象,但是这种对象还有其他功能。常用的auto_ptr,unique_ptr和shared_ptr三种智能指针模板,可帮助我们管理动态内存分配。
简单看看下面的例子:

void remodel(string &str)
{
	string *ps = new string(str);
	...
	str = *ps;
	return;
}

这个函数是存在缺陷的。每当调用时,该函数都会分配内存,但从来不收回,从而导致内存泄露。解决的方法就是再return语句之前加上delete ps;但是有时可能忘记,有时可能被注释了。简单分析这个函数,当remodel()这样的函数终止,本地变量都将从栈内存中中删除,因此指针ps占据的内存将被释放。如果ps指向的内存也被释放多好啊。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。但问题是ps只是一个常规的指针,不是有析构函数的类对象。
如果它是对象,则可以在对象过期时,让他的析构函数删除指向的内存,这正是智能指针背后的原理。
auto_ptr是c++98提供的解决方案,c++1已经抛弃,并提供了另外的两种方法。

使用智能指针
修改上面的remodel()函数:
1.包含头文件memory
2.修改指针
3.删除delete语句

void remodel(string &str)
{
	auto_ptr<string> ps (new string(str));
	//...
	str = *ps;
	//delte ps;no longer needed
	return;
}

智能指针的构造:
所有的智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。不需要自动将指针转换成智能指针对象:

shared_ptr<double>pd;
double *p_reg = new double;

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

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

使用智能指针应该避免一点:

string info1("test");
shared_ptr<string> pinfo(&info1);

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

有关使用智能指针的注意事项:
1.智能指针的赋值

auto_ptr<string> pinfo(new string("test"));
auto_ptr<string> pp;
pp = pinfo;

如果pinfo和pp是常规指针,则两个指针将指向同一个string对象。上面的程序运行阶段崩溃,对象的所有权转移到了pp,当程序试图删除同一个对象两次,一次是pinfo过期,另一次是pp过期,要避免这种问题,方法很多种:

  • 定义赋值运算符,执行深复制。这样两个指针指向不同的对象,其中一个对象是另一个对象的副本。
  • 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可以拥有它,这样只有拥有该对象的智能指针的析构函数可以删除该对象。然后,让赋值操作转让所有权。这是用于auto_ptr和unique_ptr的策略,unique_ptr的策略更严格。
  • 创建更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。仅当最后一个指针过期,才调用delete,这是shared_ptr的策略。

因此上述的代码改成shared_ptr是没有问题的。

auto_ptr和unique_ptr对比:
上述的代码中,pp接管了string对象的所有权后,pinfo的所有权被剥夺,但如果程序随后试图使用pinfo,这将出现问题,因为pinfo不再指向有效的数据。

使用将auto_ptr改成unique_ptr:

unique_ptr<string> pinfo(new string("test"));
unique_ptr<string> pp;
pp = pinfo;//error

编译器认为pp = pinfo非法。避免了pinfo不再指向有效数据的问题。
因此unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

但有时候将一个智能指针赋给另一个并不会留下危险的悬挂指针。比如下面的程序:

unique_ptr<string> demo(const char*s)
{
	unique_ptr<string>tmp(new string(s));
	return tmp;
}
unique_ptr<string>p3;
p3 = demo("dadada");

demo()函数返回一个临时unique_ptr,然后p3接管了原本返回unique_ptr所有的对象,而返回的unique_ptr被销毁。这没问题,因为p3拥有string对象的所有权。但这里的另外一个好处就是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是编译器确实允许这种赋值。
总之,程序试图将一个unique_ptr赋给另一个,如果unique_ptr是临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。

unique_ptr<string>p3;
p3 = unique_ptr<string>(new string("ahhaha"));

上面的代码不会留下悬挂的unique_ptr指针,因为调用了unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给p3后被销毁。

相对于auto_ptr,unique_ptr还有一个优点就是可以用于数组的变体。模板auto_ptr使用delete而不是delete[],因此只能和new一起用,而不能和new[]一起用。但unique_ptr有使用new[]和delete[]的版本:

unique_ptr<double[]>pda(new double(5));

注:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存,使用unique_ptr。

代码实现:

//Shared_ptr.h
#ifndef SHARED_PTR_H_
#define SHARED_PTR_H_
#include<iostream>
using namespace std;
template<typename T>
class Shared_ptr
{
private:
	T*m_ptr;
	size_t*m_count;
public:
	//构造函数
	Shared_ptr() :m_ptr(nullptr), m_count(new size_t) {}

	Shared_ptr(T*ptr) :m_ptr(ptr), m_count(new size_t) { *m_count = 1; }

	//拷贝构造函数
	Shared_ptr(const Shared_ptr&ptr) 
	{
		m_ptr = ptr.m_ptr;
		m_count = ptr.m_count;
		++(*m_count);
	}

	//拷贝赋值函数
	void operator=(const Shared_ptr&ptr)
	{
		Shared_ptr(move(ptr));
	}

	//移动构造函数
	Shared_ptr(Shared_ptr&&ptr) :m_ptr(ptr.m_ptr), m_count(ptr.m_count) { ++(*m_count); }

	//移动赋值函数
	void operator=(const Shared_ptr&&ptr) { Shared_ptr(move(ptr)); }

	//箭头运算符
	T* operator->() { return m_ptr; }

	//解引用运算符
	T& operator*() { return *m_ptr; }

	//重载bool运算符
	operator bool() { return m_ptr == nullptr; }

	T*get() { return m_ptr; }

	size_t use_count() { return *m_count; }

	bool unique() { return *m_count == 1; }

	void swap(Shared_ptr&ptr)
	{
		swap(*this, ptr);
	}

	//析构函数
	~Shared_ptr()
	{
		--(*m_count);
		if (*m_count == 0)
		{
			delete m_ptr;
			delete m_count;
			m_ptr = nullptr;
			m_count = nullptr;
		}
	}

};


class Person
{
private:
	int m_Age;
	string m_Name;
public:
	Person(int age, string name)
	{
		cout << "Person(int age,string name):" << age << " " << name <<  endl;
		m_Age = age;
		m_Name = name;
	}
	~Person()
	{

		cout << "~Person()" << m_Age << " " << m_Name << endl;
	}
	//显示人物信息
	void showInfo()
	{
		cout << "name = " << m_Name << ", age = " << m_Age << endl;
	}
};
#endif

//mian.cpp
void mian()
{
	Shared_ptr<Person> p1 = Shared_ptr<Person>(new 		Person(5,"hhh"));
	(p1.get())->showInfo();
	p1->showInfo();
	(*p1).showInfo();

	 cout << p1.operator bool() << endl;
	 cout << p1.use_count() << endl;
	 cout << p1.unique() << endl;

	 Shared_ptr<Person> p2= Shared_ptr<Person>(p1);

	 cout << p1.operator bool() << endl;
	 cout << p1.use_count() << endl;
	 cout << p1.unique() << endl;

	 cout << p2.operator bool() << endl;
	 cout << p2.use_count() << endl;
	 cout << p2.unique() << endl;

	 Shared_ptr<Person> p3 = getInst();
	 int n = p3.use_count();
	 cout << n << endl;

	 int m=getInst().use_count();
	 cout << m << endl;
 }

在这里插入图片描述

参考:
C++移动构造函数以及move语句简单介绍
https://www.cnblogs.com/qingergege/p/7607089.html
四种智能指针的用法和原理
https://blog.youkuaiyun.com/sinat_36118270/article/details/69061348
c++智能指针-知乎
https://zhuanlan.zhihu.com/p/54078587

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值