为什么要用智能指针
为了降低内存泄漏的风险,所以提出了智能指针的概念;智能指针就是通过这个原理来解决指针自动释放的问题!
C++98提供了auto_ptr模板的解决方案
C++11增加了unique_ptr、shared_ptr和weak_ptr
auto_ptr使用详解(c++98)
auto_ptr是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete来释放内存!
用法:
头文件: #include
用法: auto_ptr<类型> 变量名(new 类型)
例如:
auto_ptr str(new string(“我要成为大牛~”));
auto_ptr<vector> av(new vector(10));
代码如下:
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
#include <memory>
using namespace std;
//auto_ptr< Test > t(new Test())//忠告1:尽可能不要将auto_ptr变量定义为全局变量
class Test
{
public:
Test() {
cout << "Test is construct" << endl;
debug = 1;
}
~Test() { cout << "Test is destruct" << endl; }
int getDebug() {
return debug;
}
private:
int debug;
};
class FileNotFoundException : public std::exception {
public:
const char* what() const throw() {
return "文件不存在";
}
};
//用法:auto_ptr<类型> 变量名(new 类型)
void memory_leak_demo1()
{
auto_ptr< Test > t(new Test());
//忠告3:除非自己知道后果,不要把auto_ptr智能指针赋值给同类型的另外一个智能指针
// auto_ptr<Test> t1;
// t1 = t;
//Test* t = new Test() ;
//auto_ptr<Test>* tp = new auto_ptr<Test>(new Test()); //忠告2:尽可能不要将auto_ptr变量定义为指针
//在使用智能指针访问对象时,使用方式和普通指针一样
cout << "-> debug:" << t->getDebug() << endl;
cout << "* debug:" << (*t).getDebug() << endl;
// Test* tmp = t.get();
// cout << "get debug:" << t->getDebug() << endl;
// Test* tmp = t.release();//release 取消智能指针对动态内存的托管,之前分配的内存必须手动释放
// delete tmp;
//reset重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
//t.reset();
t.reset(new Test());//先创建一个新的对象,然后原来的被析构掉,然后自己的生命周期结束之后,自己会被析构掉
if(0){
Test* t1 = new Test();
t1->getDebug();
}
return;
}
int memory_leak_demo2()
{
// Test* t = new Test();
auto_ptr< Test > t(new Test());
/*
* 程序执行一段复杂的逻辑,假设尝试从一个必须存在
* 的文件中读取某些数据,而文件此时不存在
*/
{
//throw exception("文件不存在");
throw FileNotFoundException();
}
//delete t;
return 0;
}
int main() {
memory_leak_demo1();
try {
memory_leak_demo2();
}
catch(const exception& e) {
cout <<"catch exception:" << e.what() << endl;
}
return 0;
}
使用建议:
- 尽可能不要将auto_ptr变量定义为全局变量(因为定义的对象要等程序全部结束以后才能被析构,达不到我们的目的,没有意义)或者指针(因为这样不能被析构);
- 除非自己知道后果,不要把auto_ptr智能指针赋值给同类型的另外一个智能指针
- c++11后auto_ptr已经被“抛弃”,已使用unique_ptr替代
unique_ptr的用法详解(c++11)
auto_ptr是用于c++ 11之前的智能指针。由于auto_ptr基于排他所有权模式;两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr主要有两大问题:
复制和赋值会改变资源的所有权。
在STL容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
不支持对象数组的操作。
参考代码如下:
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>
using namespace std;
int main() {
//弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
// 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
auto_ptr<string> p1(new string("I'm martin."));
auto_ptr<string> p2(new string("I'm rock."));
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
p1 = p2;
printf("after p1 = p2\n");
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
vector<auto_ptr<string>> va;
auto_ptr<string> p3(new string("I'm p3."));
auto_ptr<string> p4(new string("I'm p4."));
va.push_back(std::move(p3)); //把p3右值化
va.push_back(std::move(p4));
cout << "va[0]: " << *va[0] << endl;
cout << "va[1]: " << *va[1] << endl;
//风险
va[0] = va[1];
cout << "va[0]: " << *va[0] << endl;
cout << "va[1]: " << *va[1] << endl;
//弊端3. 不支持对象数组的内存管理
//auto_ptr<int[]> ai(new int[5]); //不能被定义
// //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
// {
// auto_ptr<string> p2;
//
// string *str = new string("智能指针的内存管理陷阱");
// p2.reset(str);
// {
// auto_ptr<string> p1;
// p1.reset(str);
// }
// cout <<"str: " << *p2 << endl;
// }
return 0;
}
所以,C++11用更严谨的unique_ptr取代了auto_ptr!
unique_ptr特性:
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值赋值操作,但允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当他本身离开作用域时会自动释放它所指的对象
- 在容器中保存指针是安全的
参考代码如下:
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>
using namespace std;
int main1() {
//弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
// 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
auto_ptr<string> p1(new string("I'm martin."));
auto_ptr<string> p2(new string("I'm rock."));
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
p1 = p2;
printf("after p1 = p2\n");
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
vector<auto_ptr<string>> va;
auto_ptr<string> p3(new string("I'm p3."));
auto_ptr<string> p4(new string("I'm p4."));
va.push_back(std::move(p3)); //把p3右值化
va.push_back(std::move(p4));
cout << "va[0]: " << *va[0] << endl;
cout << "va[1]: " << *va[1] << endl;
//风险
va[0] = va[1];
cout << "va[0]: " << *va[0] << endl;
cout << "va[1]: " << *va[1] << endl;
//弊端3. 不支持对象数组的内存管理
//auto_ptr<int[]> ai(new int[5]); //不能被定义
// //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
// {
// auto_ptr<string> p2;
//
// string *str = new string("智能指针的内存管理陷阱");
// p2.reset(str);
// {
// auto_ptr<string> p1;
// p1.reset(str);
// }
// cout <<"str: " << *p2 << endl;
// }
return 0;
}
int main() {
//弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
// 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
//unique_ptr如何解决这个问题?不允许显示的右值赋值和构造
unique_ptr<string> p1(new string("I'm martin."));
unique_ptr<string> p2(new string("I'm rock."));
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
//如果一定要转移,使用move把左值转成右值
p1 = std::move(p2);
printf("p1:%p\n",p1.get());
printf("p2:%p\n",p2.get());
//p1 = p2; //左值赋值禁止
unique_ptr<string> p3(new string("I'm p3."));
unique_ptr<string> p4(std::move(p3)); //左值拷贝构造也不行,必须转换成右值
//弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
vector<unique_ptr<string>> vu;
unique_ptr<string> p5(new string("I'm p5."));
unique_ptr<string> p6(new string("I'm p6."));
vu.push_back(std::move(p3)); //把p3右值化
vu.push_back(std::move(p4));
cout << "vu[0]: " << *vu[0] << endl;
cout << "vu[1]: " << *vu[1] << endl;
//风险
//vu[0] = vu[1]; //将1交给0去做,1就废掉了,对于unique_ptr是显式的,但对于auto_ptr是隐式的
cout << "vu[0]: " << *vu[0] << endl;
cout << "vu[1]: " << *vu[1] << endl;
//弊端3. auto_ptr不支持对象数组的内存管理
//但是unique_ptr支持对向数组的管理
unique_ptr<int[]> ui(new int[5]);//自动会调用delete[]函数去释放
//auto_ptr<int[]> ai(new int[5]); //不能被定义
// //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
// {
// auto_ptr<string> p2;
//
// string *str = new string("智能指针的内存管理陷阱");
// p2.reset(str);
// {
// auto_ptr<string> p1;
// p1.reset(str);
// }
// cout <<"str: " << *p2 << endl;
// }*/
return 0;
}
构造函数
unique_ptr< T > up; //空的unique_ptr,可以指向类型为T的对象
unique_ptr< T > up1(new T());//定义unique_ptr,同时指向类型为T的对象
unique_ptr<T[]> up;//空的unique_ptr,可以指向类型为T的数组对象
unique_ptr<T[]> up1(new T());//定义unique_ptr,同时指向类型为T的数组对象
unique_ptr<T,D> up();//空的unique_ptr,接受一个D类型的删除器d,使用d释放内存
unique_ptr<T,D> up(new T());//定义unique_ptr,同时指向类型为T的数组对象,接受一个D类型的删除器d,使用删除器释放内存
赋值
unique_ptr< int > up1(new int(10));
unique_ptr< int > up2(new int(11));
up1 = std::move(up2);//必须使用移动语义,结果up1内存释放,up2交由up1管理
主动释放对象
up = nullptr;//释放up指向的对象,将up置为空
或 up = NULL;//作用相同
放弃对象控制权
up.release;//放弃对象的控制权,返回指针,将up置为空,不会·释放内存
交换
up.swap(up1);//将智能指针up和up1管控的对象进行交换
shared_ptr使用详解(C++11)
构造函数
shared_ptr< T > sp; //空的shared_ptr,可以指向类型为T的对象
shared_ptr< T > sp1(new T()); //定义sherad_ptr,同时指向类型为T的对象
shared_ptr< T[] > sp2; //空的shared_ptr,可以指向类型为T的数组对象( c++17支持)
shared_ptr< T[] > sp3(new T[]{…}); //指向类型为T的数组对象( c++17支持)
shared_ptr< T > sp4(NULL,D()); //空的shard_ptr,接受一个D类型的删除器,使用D释放内存
shared_ptr< T > sp5(new T(),D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存
代码如下:
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _no) : no(_no)
{
cout << "Construct " << no << endl;
}
~Person()
{
cout << "Destruct " << no << endl;
}
int no;
};
class DestructPerson
{
public:
void operator()(Person* pt)
{
cout << " DestructPerson..." << endl;
delete pt;
}
};
int main()
{
shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象
cout << "sp1 ref_counter:" << sp1.use_count() << endl;
cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.
sp1 = sp2;
cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;
shared_ptr<Person> sp3(sp1);
cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针
//数组对象的管理
shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]
shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象
return 0;
}
初始化
方式一 构造函数
shared_ptr< int > up1(new int(10));// int(10)的引用计数为1
shared_ptr< int > up2(up1);//使用智能指针up1构造up2,此时int(10)引用计数为2
方式二 使用make_shared初始化对象,分配内存效率更高
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr;
用法:
make_shared< 类型 >(构造类型对象需要的参数列表)
shared_ptr< int > p4 = make_shared< int >(2);// 多个参数以逗号’,'隔开,最多接受十个
shared_ptr< string > p4 = make_shared< string >(“字符串”);
参考代码如下:
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _no) : no(_no)
{
cout << "Construct " << no << endl;
}
~Person()
{
cout << "Destruct " << no << endl;
}
int no;
};
class DestructPerson
{
public:
void operator()(Person* pt)
{
cout << " DestructPerson..." << endl;
delete pt;
}
};
int main()
{
shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象
cout << "sp1 ref_counter:" << sp1.use_count() << endl;
cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.
sp1 = sp2;
cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;
shared_ptr<Person> sp3(sp1);
cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针
//数组对象的管理
shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]
shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象
//使用 make_ptr 函数模板初始化
shared_ptr<Person> sp9;
sp9 = make_shared<Person>(9);
cout << "after sp9 = make_shared<Person>(9),sp9 ref_counter:" << sp9.use_count() << endl;// 当前管控Person(2)的共享指针的数量.
return 0;
}
赋值
shared_ptr< int > up1(new int(10));// int(10)的引用计数为1
shared_ptr< int > up2(new int(11));// int(11)的引用计数为1
up1 = up2;//int(10)的引用计数减1,计数归零内存释放,up2共享int(11)给up1,int(11)的引用计数为2
主动释放对象
shared_ptr< int > up1(new int(10));
up1 = nullptr;//int(10)的引用计数减1,计数归零后内存释放
或
up1 = NULL;//作用同上
重置
up.reset(); //将up重置为空指针,所管理对象引用计数 减1
up.reset(p1);//将up重置为p1(的值),up管控的对象计数减1,up接管对p1指针的管控
up.reset(p1,d);//将up重置为p1(的值),up管控的对象计数减1,并使用d作为删除器
交换
std::swqp(p1,p2); //交换p1和p2管理的对象,原对象的引用计数不变
p1.swap(p2);//同上
代码如下:
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _no) : no(_no)
{
cout << "Construct " << no << endl;
}
~Person()
{
cout << "Destruct " << no << endl;
}
int no;
};
class DestructPerson
{
public:
void operator()(Person* pt)
{
cout << " DestructPerson..." << endl;
delete pt;
}
};
int main()
{
shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象
cout << "sp1 ref_counter:" << sp1.use_count() << endl;
cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.
sp1 = sp2;
cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;
shared_ptr<Person> sp3(sp1);
cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针
//数组对象的管理
shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]
shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象
//使用 make_ptr 函数模板初始化
shared_ptr<Person> sp9;
sp9 = make_shared<Person>(9);
cout << "after sp9 = make_shared<Person>(9),sp9 ref_counter:" << sp9.use_count() << endl;// 当前管控Person(2)的共享指针的数量.
//重置
// shared_ptr<Person> sp10;
// Person* p10 = new Person(10);
// sp9.reset(p10);
// cout << "after sp9.reset(p10),sp9 ref_counter:" << sp9.use_count() << endl;
shared_ptr<Person> sp10 = sp9;
Person* p10 = new Person(10);
sp9.reset(p10);
cout << "after sp9.reset(10),sp9 ref_counter: " << sp9.use_count() << endl;
std::swap(sp9,sp10);
cout << "sp9.swap(sp10) 交换后,sp9: " << sp9->no << " sp10: " << sp10->no << endl;
return 0;
}
使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!
代码如下:
#include <iostream>
#include <memory>
#include <stdio.h>
#include <vector>
using namespace std;
class girl;
class boy
{
public:
boy()
{
cout << "boy construct!" << endl;
}
~boy()
{
cout << "boy destruct!" << endl;
}
void set_girl_friend(shared_ptr<girl>& g)
{
girl_friend = g;
}
private:
shared_ptr<girl> girl_friend;
};
class girl
{
public:
girl()
{
cout << "girl construct!" << endl;
}
~girl()
{
cout << "girl destruct!" << endl;
}
void set_boy_friend(shared_ptr<boy>& b)
{
boy_friend = b;
}
private:
shared_ptr<boy> boy_friend;
};
void use_trap()
{
shared_ptr<girl> sp_girl(new girl()); //白娘子
shared_ptr<boy> sp_boy(new boy()); //许仙
sp_girl->set_boy_friend(sp_boy);
sp_boy->set_girl_friend(sp_girl);
}
int main() {
use_trap();
return 0;
}
weak_ptr
weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助工作,主要用于解决共享指针可能引发的循环引用问题,而循环引用指的是两个或多个对象相互持有对方的共有指针,导致它们无法被正确地释放,从而造成内存泄漏,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起记数的增加或减少,同时weak_ptr没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
代码如下:
#include <iostream>
#include <memory>
#include <stdio.h>
#include <vector>
using namespace std;
class girl;
class boy
{
public:
boy()
{
cout << "boy construct!" << endl;
}
~boy()
{
cout << "boy destruct!" << endl;
}
void set_girl_friend(shared_ptr<girl>& g)
{
girl_friend = g;
}
private:
shared_ptr<girl> girl_friend;
};
class girl
{
public:
girl()
{
cout << "girl construct!" << endl;
}
~girl()
{
cout << "girl destruct!" << endl;
}
void set_boy_friend(shared_ptr<boy>& b)
{
boy_friend = b;
}
private:
shared_ptr<boy> boy_friend;
};
void use_trap()
{
shared_ptr<girl> sp_girl(new girl()); //白娘子
shared_ptr<boy> sp_boy(new boy()); //许仙
//弱指针的使用
weak_ptr<girl> wp_girl; //定义空的弱指针
weak_ptr<girl> wp_girl2(sp_girl); //使用共享构造
wp_girl = sp_girl; //允许共享指针赋值给弱指针
cout << "sp_girl ref_count:" << sp_girl.use_count() << endl;
cout << "wp_girl.use_count:" << wp_girl.use_count() << endl;
//(*sp_girl).set_boy_frined(sp_boy); 弱指针不支持* 和 ->对指针访问
//sp_girl->set_boy(sp_boy);
//在必要的时候可以转成共享指针
shared_ptr<girl> sp_girl1;
sp_girl1 = wp_girl.lock();
cout << "after locked,wp_girl.use_count:" << wp_girl.use_count() << endl;
sp_girl1 = NULL;
sp_girl->set_boy_friend(sp_boy);
//sp_boy->set_girl_friend(sp_girl);
}
int main() {
use_trap();
return 0;
}
根据上述代码可知,假如我们不使用弱指针,将会产生循环引用问题,如下图所示:
如图所示,由于他们互相持有对方的共享指针,导致它们的引用计数无法变为零,从而无法释放内存,形成了循环引用,这个时候就需要weak_ptr来解决这个问题。
并且我们可以使用weak_ptr的lock()成员函数来获取一个可用的shared_ptr对象,该函数返回一个shared_ptr,指向weak_ptr所指的对象(如果对象仍然存在),否则返回一个空的shared_ptr,他在处理循环引用时可以检查对象的有效性。