C++-智能指针

首先我们讲一下普通指针的不足

  • newnew[]的内存需要用deletedelete[]释放。
  • 程序员的主观失误,忘了或漏了释放。
  • 程序员也不确定何时释放。

针对这个问题,程序员也想到了一些办法:

  • 类内的指针,在析构函数中释放。
  • C++内置数据类型,如何释放?
  • new出来的类,本身如何释放?

所以就有了智能指针:

  • 智能指针是类模板,在栈上创建智能指针对象。
  • 把普通指针交给智能指针对象。
  • 智能指针对象过期时,调用析构函数释放普通指针的内存。

智能指针-unique ptr基础

unique_ptr独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁。
包含头文件:#include<memory>

template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
	explicit unique_ptr(pointer p) noexcept;// 不可用于转换函数。
	~unique_ptr() noexcept;
	T& operator*() const;// 重载 * 操作符。
	T * operator->() const noexcept;//重载->操作符。
	unique_ptr(const unique_ptr&) = delete;// 禁用拷贝构造函数。
	unique_ptr & operator=(const unique_ptr&) = delete;// 禁用赋值函数。 
	unique_ptr(unique_ptr&&)noexcept;// 右值引用。
	unique_ptr & operator=(unique_ptr&&) noexcept;//右值引用。ll ..e
	//...
private:
	pointer ptr;


};

我们会发现其中赋值函数还有拷贝构造函数都是禁用的.因为unique_ptr的设计目标是独享,一个unique_ptr对象只对一个资源负责.如果unique_ptr对象允许复制,那么就会出现多个unique_ptr对象指向同一块内存的情况.当其中一个unique_ptr对象过期的时候,释放内存,那么其他的unique_ptr对象过期的时候,又会释放内存,造成的结果是对同块内存释放多次,就成了操作野指针.

基本使用方法

  1. 初始化
    方法一:
    unique_ptr<AA> p0(new AA("西施"));//分配内存并初始化。
    
    方法二:
    unique_ptr<AA> p0 = make_unique<AA>("西施");//C++14标准。
    
    方法三:
    AA* p = new AA("西施");
    unique_ptr<AA> p0(p);//用已存在的地址初始化。
    
    其中第一种最常用
    #define _CRT_SECUER_NO_WARNINGS
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<list>
    #include<map>
    using namespace std;
    
    class AA {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA().\n"; }
    	AA(const string& name) :m_name(name) {
    		cout << "调用构造函数AA(" << m_name << ").\n";
    	}
    	~AA() {
    		cout << "调用了析构函数~AA(" << m_name << ")\n";
    	}
    };
    int main() {
    	unique_ptr<AA> pu1(new AA("西施"));//在这里面没有写delete,也调用了析构函数
    	
    	cout << "m_name=" << (*pu1).m_name << endl;
    	cout << "m_name=" << pu1->m_name << endl;
    	
    	//delete p;
    }
    
    要注意unique_ptr对象不允许复制,也就没有拷贝构造函数,也不能用=进行赋值.
    #define _CRT_SECUER_NO_WARNINGS
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<list>
    #include<map>
    using namespace std;
    
    class AA {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA().\n"; }
    	AA(const string& name) :m_name(name) {
    		cout << "调用构造函数AA(" << m_name << ").\n";
    	}
    	~AA() {
    		cout << "调用了析构函数~AA(" << m_name << ")\n";
    	}
    };
    int main() {
    	unique_ptr<AA> pu1(new AA("西施"));//在这里面没有写delete,也调用了析构函数
    	
    	cout << "m_name=" << (*pu1).m_name << endl;
    	cout << "m_name=" << pu1->m_name << endl;
    	AA* p = new AA("西施");
    	//unique_ptr<AA>pu2 = p;//错误,不能把普通指针直接赋值给智能指针
    	//unique _ptr<AA> pu3 = new AA("西施");//错误,不能把普通指针直接赋给智能指针。
    	
    	// unique_ptr<AA> pu2 = pu1;//错误,不能用其它unique_ptr拷贝构造。
    	
    	//unique_ptr<AA> pu3;
    	//pu3 = pu1;// 错误,不能用 = 对unique ptr进行赋值。
    
    
    
    	//delete p;
    }
    
  2. 使用方法:
  • 智能指针重载了*->操作符,可以像使用指针一样使用unique_ptr
  • 不支持普通的拷贝和赋值。-
    AA* p = new AA("西施");
    unique_ptr<AA> pu2 = p;//错误,不能把普通指针直接赋给智能指针。
    unique_ptr<AA> pu3 = new AA("西施");//错误,不能把普通指针直接赋给智能指针。
    unique_ptr<AA> pu2 = pu1;//错误,不能用其它unique_ptr拷贝构造。
    unique_ptr<AA> pu3;
    pu3 = pu1;//错误,不能用=对unique_ptr进行赋值。
    
  • 不要用同一个裸指针初始化多个unique_ptr
  • get()方法返回裸指针。
  • 不要用unique_ptr管理不是new分配的内存。
  1. 用于函数的参数
  • 传引用(不能传值,因为unique_ptr 没有拷贝构造函数)
    #define _CRT_SECUER_NO_WARNINGS
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<list>
    #include<map>
    using namespace std;
    
    class AA {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA().\n"; }
    	AA(const string& name) :m_name(name) {
    		cout << "调用构造函数AA(" << m_name << ").\n";
    	}
    	~AA() {
    		cout << "调用了析构函数~AA(" << m_name << ")\n";
    	}
    };
    void func(unique_ptr<AA>& pp) {//传引用
    	cout << "name=" << pp->m_name << endl;
    }
    int main() {
    	AA* p = new AA("西施");
    	unique_ptr<AA>pu(p);
    	func(pu);
    	
    }
    
  • 裸指针。
  1. 不支持指针的运算(+,-,++,–)

智能指针-unique ptr更多技巧

  1. 将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。
    unique_ptr<AA> p0;
    p0 = unique_ptr<AA>(new AA("西瓜"));
    
    #define _CRT_SECUER_NO_WARNINGS
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<list>
    #include<map>
    using namespace std;
    class AA {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA().\n"; }
    	AA(const string& name) :m_name(name) {
    		cout << "调用构造函数AA(" << m_name << ").\n";
    	}
    	~AA() {
    		cout << "调用了析构函数~AA(" << m_name << ")\n";
    	}
    };
    
    unique_ptr<AA> func() {
    	unique_ptr<AA> pp(new AA("西施3"));
    	return pp;
    }
    
    int main() {
    	unique_ptr<AA> pu1(new AA("西施1"));
    
    	unique_ptr<AA>pu2;
    	//pu2 = pu1;//错误
    	pu2 = unique_ptr<AA>(new AA("西施2"));
    	cout << "调用了func()之前\n";
    	pu2 = func();//用函数的返回值复制
    	cout << "调用了func()之后\n";
    }
    
  2. 用nullptr给unique_ptr赋值将释放对象,空的unique_ptr==nullptr.
  3. release()释放对指针的控制权,将unique_ptr置为空,返回裸指针。(可用于把unique_ptr传递给子函数,并且在子函数中释放对象)
  4. std:move()可以转移对指针的控制权。(可用于把unique_ptr传递给子函数,并且在子函数中
  5. reset()释放对象。
    void reset(T * _ptr= (T *) nullptr);
    pp.reset();//释放pp对象指向的资源对象。
    pp.reset(nullptr);//释放pp对象指向的资源对象														    							
    pp.reset(new AA("bbb"));//释放pp指向的资源对象,同时指向新的对象。
    
  6. swap()交换两个unique_ptr的控制权。
    void swap(unique_ptr<T> &_Right);
    
  7. unique_ ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
  8. unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。-
  9. unique_ptr提供了支持数组的具体化版本。
    数组版本的unique_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。

智能指针shared_ptr

shared_ptr共享它指向的对象,多个shared_ptr可以指向(关联)相同的对象,在内部采用计数机
制来实现。
当新的shared_ptr与对象关联时,引用计数增加1
shared_ptr超出作用域时,引用计数减1。当引用计数变为0时,则表示没有任何shared_ptr与对象关联,则释放该对象。

基本用法

shared_ptr的构造函数也是explicit,但是,没有删除拷贝构造函数和赋值函数。
初始化:
方法一:

shared_ptr<AA> p0(new AA("西施"));//分配内存并初始化。

方法二:

shared_ptr<AA> p0 = make_shared<AA>("西施");//C++11标准,效率更高。

方法三:

AA* p = new AA("西施");
shared_ptr<AA> p0(p);//用已存在的地址初始化。

方法四:

shared_ptr<AA> p0(new AA("西施"));
shared_ptr<AA> p1(p0);//用已存在的shared_ptr初始化,计数加1。
shared_ptr<AA> p1=p0;//用已存在的shared_ptr初始化,计数加1。
#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
};



int main() {
	AA* p = new AA("西施");
	
	shared_ptr<AA>p0(p);//用已经存在的地址初始化
	
	shared_ptr<AA>p1=p0;//用已存在的shared_ptr拷贝构造,计数+1
	
	shared_ptr<AA>p2 = p0;//用已存在的shared_ptr拷贝构造,计数+1

	cout << "p0.use_count()=" << p0.use_count() << endl;//显示p0引用计数
	cout << "m_name" << p0->m_name << endl;
	cout << "p0.get()" << p0.get() << endl;
	
	cout << "p1.use_count()=" << p1.use_count() << endl;//显示p1引用计数
	cout << "m_name" << p1->m_name << endl;
	cout << "p1.get()" << p1.get() << endl;

	cout << "p2.use_count()=" << p2.use_count() << endl;//显示p1引用计数
	cout << "m_name" << p2->m_name << endl;
	cout << "p2.get()" << p2.get() << endl;
}

使用方法

  • 智能指针重载了*->操作符,可以像使用指针一样使用shared_ptr
  • use_count()方法返回引用计数器的值。
  • unique()方法,如果use_count()为1,返回true,否则返回false
  • 支持普通的拷贝和赋值,左值的shared_ptr的计数器将减1,右值shared_ptr的计算器将加1。
    #define _CRT_SECUER_NO_WARNINGS
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<list>
    #include<map>
    using namespace std;
    class AA {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA().\n"; }
    	AA(const string& name) :m_name(name) {
    		cout << "调用构造函数AA(" << m_name << ").\n";
    	}
    	~AA() {
    		cout << "调用了析构函数~AA(" << m_name << ")\n";
    	}
    };
    
    
    
    int main() {
    	shared_ptr<AA> pa0(new AA("西施a"));//初始化资源西施a。
    	shared_ptr<AA> pa1 = pa0;//用已存在的shared_ptr拷贝构造,计数加1。
    	shared_ptr<AA> pa2 = pa0;//用已存在的shared_ptr拷贝构造,计数加1。
    	
    	cout << "pa0.use_count()=" << pa0.use_count() << endl;//值为3。
    	
    	shared_ptr<AA> pb0(new AA("西施b"));//初始化资源西施b。
    	shared_ptr<AA> pb1 = pb0;//用已存在的shared_ptr拷贝构造,计数加1。
    	cout << "pb0.use_count()=" << pb0.use_count() << endl;//值为2。
    	
    	pb1 = pa1;//将西施a的赋值给西施b
    	
    	cout << "pa0.use_count()=" << pa0.use_count() << endl;//值为4。
    	cout << "pb0.use_count()=" << pb0.use_count() << endl;//值为2。
    
    }
    
  • get()方法返回裸指针。
  • 不要用同一个裸指针初始化多个shared_ptr
  • 不要用shared_ptr管理不是new分配的内存。
  1. 用于函数的参数
    unique_ptr的原理相同。
  2. 不支持指针的运算(+、-、++、–)

注意:如果unique_ptr能解决问题,就不要用shared_ptrunique_ptr的效率更高,占用的资源更少。

智能指针的删除器

在默认情况下,智能指针过期的时候,用delete 原始指针;释放它管理的资源。
程序员可以自定义删除器,改变智能指针释放资源的行为。
删除器可以是全局函数、仿函数和Lambda表达式,形参为原始指针。

#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
};

void deletefunc(AA* a) {//删除器,普通函数
	cout << "自定义删除器(全局函数)。\n";
	delete a;
}


struct deleteclass {
	void operator()(AA* a) {
		cout << "自定义删除器(仿函数)。\n";
		delete a;
	}
};

auto deleterlamb = [](AA* a) {
	cout << "自定义删除器(Lambda)。\n";
	delete a;
};

int main() {
	shared_ptr<AA> pa1(new AA("西施a"));//用缺省的删除器
	shared_ptr<AA> pa1(new AA("西施a"), deletefunc);//删除器,普通函数。
	//shared_ptr<AA> pa2(new AA("西施b"), deleteclass());//删除器,仿函数。
	//shared_ptr<AA> pa3(new AA("西施c"), deleterlamb);//删除器,Lambda表达式。
	//unique_ptr<AA,decltype(deletefunc)*> pu1(new AA("西施1"), deletefunc);
	//unique_ptr<AA, deleteclass> pu2(new AA("西施2"), deleteclass());
	//unique_ptr<AA, decltype(deleterlamb)> pu3(new AA("西施3"), deleterlamb);



}

智能指针weak_ptr

shared_ptr存在的问题

shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源。
如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放。
首先我们看这个代码,就是循环引用:

#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class BB;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
	shared_ptr<BB> m_p;
};


class BB {
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB().\n"; }
	BB(const string& name) :m_name(name) {
		cout << "调用构造函数BB(" << m_name << ").\n";
	}
	~BB() {
		cout << "调用了析构函数~BB(" << m_name << ")\n";
	}
	shared_ptr<AA> m_p;
};

int main() {
	shared_ptr<AA>pa = make_shared<AA>("西施a");
	shared_ptr<BB>pb = make_shared<BB>("西施b");
	pa->m_p = pb;
	pb->m_p = pa;

}

运行的时候不调用析构函数。
在这里插入图片描述

然后引入了weak_ptr

#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class BB;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
	weak_ptr<BB> m_p;
};


class BB {
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB().\n"; }
	BB(const string& name) :m_name(name) {
		cout << "调用构造函数BB(" << m_name << ").\n";
	}
	~BB() {
		cout << "调用了析构函数~BB(" << m_name << ")\n";
	}
	weak_ptr<AA> m_p;
};

int main() {
	shared_ptr<AA>pa = make_shared<AA>("西施a");
	shared_ptr<BB>pb = make_shared<BB>("西施b");
	pa->m_p = pb;
	pb->m_p = pa;

}

在这里插入图片描述

weak_ptr是什么

weak_ptr是为了配合shared_ptr而引入的,它指向一个由shared_ptr管理的资源但不影响资源
的生命周期。也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放。
weak_ptr更像是shared_ptr的助手而不是智能指针。

#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class BB;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
	weak_ptr<BB> m_p;
};


class BB {
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB().\n"; }
	BB(const string& name) :m_name(name) {
		cout << "调用构造函数BB(" << m_name << ").\n";
	}
	~BB() {
		cout << "调用了析构函数~BB(" << m_name << ")\n";
	}
	weak_ptr<AA> m_p;
};

int main() {
	shared_ptr<AA>pa = make_shared<AA>("西施a");
	shared_ptr<BB>pb = make_shared<BB>("西施b");

	cout << "pa.use_count()=" << pa.use_count() << endl;
	cout << "pb.use_count()=" << pb.use_count() << endl;

	pa->m_p = pb;
	pb->m_p = pa;

	cout << "pa.use_count()=" << pa.use_count() << endl;
	cout << "pb.use_count()=" << pb.use_count() << endl;

}

如何使用weak_ptr

weak_ptr没有重载->*操作符,不能直接访问资源。
有以下成员函数:

  1. operator=();//把shared_ptrweak_ptr赋值给weak_ptr
  2. expired();//判断它指资源是否已过期(已经被销毁)。
  3. lock();//返回shared_ptr,如果资源已过期,返回空的shared
  4. reset();//将当前weak_ptr指针置为空。
  5. swap();//交换。
#define _CRT_SECUER_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<list>
#include<map>
using namespace std;
class BB;
class AA {
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA().\n"; }
	AA(const string& name) :m_name(name) {
		cout << "调用构造函数AA(" << m_name << ").\n";
	}
	~AA() {
		cout << "调用了析构函数~AA(" << m_name << ")\n";
	}
	weak_ptr<BB> m_p;
};


class BB {
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB().\n"; }
	BB(const string& name) :m_name(name) {
		cout << "调用构造函数BB(" << m_name << ").\n";
	}
	~BB() {
		cout << "调用了析构函数~BB(" << m_name << ")\n";
	}
	weak_ptr<AA> m_p;
};

int main() {
	shared_ptr<AA>pa = make_shared<AA>("西施a");
	{
		shared_ptr<BB>pb = make_shared<BB>("西施b");



		pa->m_p = pb;
		pb->m_p = pa;
		if (pa->m_p.expired() == true)
			cout << "语句块内部:pa->m_p已经过期\n";
		else
			cout << "语句块内部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << endl;
	}
	
	if (pa->m_p.expired() == true)
		cout << "语句块内部:pa->m_p已经过期\n";
	else
		cout << "语句块内部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << endl;
}

weak_ptr不控制对象的生命周期,但是,它知道对象是否还活着。
lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr,如果对象已经死了,提升会失败,返回一个空shared_ptr。提升的行为(lock())是线程安全的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值