首先我们讲一下普通指针的不足
new
和new[]
的内存需要用delete
和delete[]
释放。- 程序员的主观失误,忘了或漏了释放。
- 程序员也不确定何时释放。
针对这个问题,程序员也想到了一些办法:
- 类内的指针,在析构函数中释放。
- 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对象过期的时候,又会释放内存,造成的结果是对同块内存释放多次,就成了操作野指针.
基本使用方法
- 初始化
方法一:
方法二:unique_ptr<AA> p0(new AA("西施"));//分配内存并初始化。
方法三:unique_ptr<AA> p0 = make_unique<AA>("西施");//C++14标准。
其中第一种最常用AA* p = new AA("西施"); unique_ptr<AA> p0(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; //delete 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; 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; }
- 使用方法:
- 智能指针重载了
*
和->
操作符,可以像使用指针一样使用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
分配的内存。
- 用于函数的参数
- 传引用(不能传值,因为
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); }
- 裸指针。
- 不支持指针的运算(+,-,++,–)
智能指针-unique ptr更多技巧
- 将一个
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"; }
- 用nullptr给unique_ptr赋值将释放对象,空的
unique_ptr==nullptr
. release()
释放对指针的控制权,将unique_ptr
置为空,返回裸指针。(可用于把unique_ptr
传递给子函数,并且在子函数中释放对象)std:move()
可以转移对指针的控制权。(可用于把unique_ptr
传递给子函数,并且在子函数中reset()
释放对象。void reset(T * _ptr= (T *) nullptr); pp.reset();//释放pp对象指向的资源对象。 pp.reset(nullptr);//释放pp对象指向的资源对象 pp.reset(new AA("bbb"));//释放pp指向的资源对象,同时指向新的对象。
swap()
交换两个unique_ptr
的控制权。void swap(unique_ptr<T> &_Right);
- unique_ ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
- unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。-
- 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
分配的内存。
- 用于函数的参数
与unique_ptr
的原理相同。 - 不支持指针的运算(+、-、++、–)
注意:如果unique_ptr
能解决问题,就不要用shared_ptr
。unique_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
没有重载->
和*
操作符,不能直接访问资源。
有以下成员函数:
operator=()
;//把shared_ptr
或weak_ptr
赋值给weak_ptr
。expired()
;//判断它指资源是否已过期(已经被销毁)。lock();
//返回shared_ptr
,如果资源已过期,返回空的shared
reset();
//将当前weak_ptr
指针置为空。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())
是线程安全的。