智能指针
1.内存泄漏
内存泄漏指的是由于疏忽或者错误导致程序未能释放已经不再使用的内存。如果长期发生泄漏,会导致操作系统,后台服务响应越来越慢,最终卡死。内存泄露主要关心两个方面: 一个就是堆内存泄露,我们使用了malloc或者new申请了堆内存,忘记使用对应的释放命令free或delete。另一个方面指的是系统资源的泄漏,比如套接字资源、文件描述符、管道资源等等。
2.智能指针概念
智能指针技术就是为了解决内存泄露推出来的解决方案。它可以很好地解决内存的分配和释放问题。 智能指针技术原理:它是采用一种叫做RAII的技术实现的,即资源获取就是初始化的缩写。具体做法就是利用对象的生命周期来控制程序中的资源。即在对象构造的时候获取资源,然后在对象的生命周期内,保持对资源的有效访问,在对象析构的时候来回收释放资源。从代码角度来说,智能指针就是一个模板类,类中重载了指针的运算符,使它可以像指针一样去使用,所以叫智能指针。同时智能指针内部一定封装了原始指针(裸指针)
智能指针模板类
/*这段代码自定义了一个简单模拟智能指针功能的类模板SmartPtr,
通过在其析构函数中释放所管理的指针指向的内存(delete ptr),
体现了智能指针利用对象生命周期来管理资源(内存)的基本思路,
对应智能指针技术原理中提到的利用 RAII 机制,即在对象构造时获取资源
(这里通过传入指针获取要管理的内存资源),在对象析构时释放资源,
以此来控制内存的分配和释放,避免出现忘记释放内存导致内存泄漏的问题。*/
//模拟一个智能指针类模板
template<class T>
class SmartPtr
{
T* ptr;//这是原始指针
public:
SmartPtr(T* p) : ptr(p) {}
~SmartPtr()
{
if (ptr!= nullptr)
{
cout << "智能指针模拟,析构的时候完成内存释放" << endl;
delete ptr;
ptr = nullptr;
}
}
};
void test01()
{
//用SmartPtr管理一个int空间
SmartPtr<int> ptr_int(new int(1));
//管理一个string类型空间
SmartPtr<string> ptr_string(new string("abc"));
}
3.智能指针分类
首先引入头文件#include ,这里面定义了三种不同类型的智能指针: 共享智能指针 shared_ptr、独占智能指针 unique_ptr、弱共享智能指针 weak_ptr
(1)共享智能指针shared_ptr
即允许多个智能指针同时管理一块有效内存,它们共享的时候有一个引用计数来保证对内存的正确使用,不冲突。具体的方式是每增加一个共享者,就引用计数+1,每减少一个共享者,引用计数-1,当计数减为0的时候,此时的共享者负责回收这块内存。这样可以保证内存只会被释放一次。共享的实现是通过拷贝构造或者赋值函数的方式来实现的。
共享智能指针的成员函数
构造函数:shared_ptr sp(要管理的内存地址); 拷贝构造:sharad_ptr sp(另一个智能指针); 辅助函数:make_shared(初始化的值);举例:shared_ptr sp=make_shared(100); use_count():查看引用计数 reset():重置方法,不带参数的用法,将一个智能指针重置,如果当前智能指针是最后一个管理者,则重置的同时需要释放内存,清空指针。如果它不是最后的管理者,只需要清空指针,同时引用计数-1,但是不释放内存。 reset(要管理的新地址):重置方法,带参数的用法,将一个智能指针重置,如果当前智能指针是最后一个管理者,则重置的同时需要释放内存,然后让它马上管理一个新地址。如果它不是最后的管理者,只需要让它管理新地址,同时引用计数-1,但是不释放内存。 get():返回智能指针中的裸指针,有时候需要用到裸指针,比如函数传参的时候,参数形式是裸指针。 operator=():赋值运算符重载函数
//共享指针
void test02()
{
//构造共享指针
shared_ptr<int> ptr(new int(1));
cout << ptr << endl;
cout << *ptr << endl;//智能指针具有指针一样的行为
*ptr = 30;//修改内存空间的值
cout << *ptr << endl;
//跟其他智能指针进行共享
shared_ptr<int> ptr1(ptr);
cout << *ptr1 << endl;//30
//辅助函数
shared_ptr<int> ptr2 = make_shared<int>(10);
cout << *ptr2 << endl;//10
shared_ptr<int> ptr3 = make_shared<int>();
//查看引用计数
cout << ptr.use_count() << endl;//2
cout << ptr1.use_count() << endl;//2
cout << ptr2.use_count() << endl;//1
cout << ptr3.use_count() << endl;//1
//赋值函数也可以完成共享
ptr3 = ptr2;//ptr3就管理了ptr2的空间
cout << *ptr3 << endl;//10
cout << ptr2.use_count() << endl;//2
cout << ptr3.use_count() << endl;//2
//重置
ptr3.reset();//ptr3不是唯一管理者,所以内存不释放,计数-1
cout << ptr2.use_count() << endl;//1
cout << ptr3.use_count() << endl;//0
ptr2.reset();//ptr2此时是唯一管理者,所以需要释放内存
cout << ptr2.use_count() << endl;//0
ptr1.reset(new int(5));//ptr1不是唯一管理者,所以不释放内存,ptr1不再管理30,30的计数-1,ptr1去管理5
cout << *ptr1 << endl;//5
cout << ptr1.use_count() << endl;//1
cout << ptr.use_count() << endl;//1
//get方法
shared_ptr<Test> ptr4(new Test(999));
Test* t1 = ptr4.get();
t1->print();
}
智能指针的删除器
删除器即智能指针内部进行内存释放的函数。有时候我们可以自定义这个删除函数,完成自己的逻辑。这么做除了可以添加自己的逻辑外,还有一个作用:那就是用于删除多个连续的自定义数据类型空间。因为默认的构造函数无法删除这类空间。对于基本来说,没有这个问题。
//智能指针的删除器:
void test03()
{
////自己定义智能指针的删除器
//shared_ptr<Test> ptr(new Test(100), [](Test* t) {delete t; t = nullptr; cout << "手写删除器完成内存释放"; });
////这么做除了可以添加自己的逻辑外,还有一个作用:那就是用于删除多个连续的自定义数据类型空间。
////shared_ptr<Test> ptr1(new Test[3]); //默认的构造函数无法删除这类空间
////对于基本类型,没有这类问题
//shared_ptr<int> ptr2(new int[3]{1, 2, 3});
//如何解决这类问题?
//1.手写删除器
shared_ptr<Test> ptr3(new Test[3], [](Test* t) {delete[]t; t = nullptr; });
//2.使用系统提供的删除器函数
shared_ptr<Test> ptr4(new Test[3], default_delete<Test[]>());
//3.在构造的时候就指明这是连续空间
shared_ptr<Test[]> ptr5(new Test[3]);
}
共享智能使用的注意事项
1.注意智能指针管理一块内存是通过初始化来完成的。不能像普通指针一样直接接收一个new的返回值 2.如果构造智能指针的时候,采用的是普通指针来构造的,此时就不能再手动回收这个普通指针,否则会造成重复释放。 3.不要用同一个裸指针初始化多个智能制造,因为也会造成多次释放。 4.当往函数传入一个智能指针参数的时候,应该先把智能制造在外部构造出来,再传入参数,不能在参数部分直接构造这个智能指针,举例: fun(shared_ptr(new int(100)),参数2,参数3...) 这样做是错误应该这样做:shared_ptr ptr(new int(100));再传入函数参数:fun(ptr,参数2,参数3...)这样做的原因是,C++的不同的编译器对函数参数的计算顺序是不一样的,为了确保各种编译器下没问题,我们才这样做。 5.禁止类一个类中返回一个管理当前对象指针(this指针)的智能指针。 6.循环引用问题:两个类互相含有可以管理对方类对象的智能指针,并且相互指向了。循环引用会造成内存泄露。 7.智能指针放到容器中的时候,由于容器的性质,容器本身就是单独的一份拷贝。所以智能指针也会给容器中拷贝构造一份。这时候就会增加引用计数。
//共享智能使用的注意事项:
void test04()
{
////1.注意智能指针管理一块内存是通过初始化来完成的。不能像普通指针一样直接接收一个new的返回值
////shared_ptr<Test> ptr1 = new Test(10);//这是错误的
//Test* p = new Test(100);//普通指针的用法
////2.如果构造智能指针的时候,采用的是普通指针来构造的,此时就不能再手动回收这个普通指针,否则会造成重复释放。
//shared_ptr<Test> pshare(p);
////delete p;//上面一行代码,p这个普通指针的内存已经交给智能制造管理,就不能在这里释放了
////p = nullptr;
//3.不要用同一个裸指针初始化多个智能制造,因为也会造成多次释放。
int* p_int = new int(6);
shared_ptr<int> pshare_int1(p_int);
shared_ptr<int> pshare_int2(p_int);
//上面两个智能指针虽然管理的是同一块内存,但没有实现共享,看下面引用计数就知道了
cout << pshare_int1.use_count() << endl;//1
cout << pshare_int2.use_count() << endl;//1
}
//5.禁止类一个类中返回一个管理当前对象指针(this指针)的智能指针。
class ABC
{
int m_num;
public:
ABC() { cout << "ABC构造" << endl; }
~ABC() { cout << "ABC析构" << endl; }
shared_ptr<ABC> getP() { return shared_ptr<ABC>(this); }
};
void test05()
{
shared_ptr<ABC> sp(new ABC);
sp->getP();
}
//6.循环引用问题:两个类互相含有可以管理对方类对象的智能指针,并且相互指向了。循环引用会造成内存泄露。
//下面设计两个类进行循环引用
class B; //前向声明
class A
{
public:
shared_ptr<B> bsp;
~A() { cout << "A的析构" << endl; }
};
class B
{
public:
shared_ptr<A> asp;
~B() { cout << "B的析构" << endl; }
};
void test06()
{
//循环引用问题
shared_ptr<A> a(new A);//a智能指针管理了A对象
cout << a.use_count() << endl;//1
shared_ptr<B> b(new B);//b智能指针管理了B对象
cout << b.use_count() << endl;//1
//下面让a和b相互引用,通过赋值,让a的属性bsp跟b进行共享,同b的属性asp跟a进行共享
a->bsp = b;
b->asp = a;
cout << a.use_count() << endl;//2
cout << b.use_count() << endl;//2
}
//7.智能指针结合容器使用要注意,容器中是一分单独的拷贝
//一个函数,将一个智能指针构造出来,然后存取容器中
void p_shared_vec(vector<shared_ptr<Test>>& vec, Test* t)
{
shared_ptr<Test> p_Test(t);//构造智能指针
vec.push_back(p_Test);//再存入容器中
cout << "当前计数:" << p_Test.use_count() << endl;//2
}
vector<shared_ptr<Test>> vec;//外部有个容器
void test07()
{
Test* t1 = new Test(10);
Test* t2 = new Test(20);
Test* t3 = new Test(30);
//调用函数存入容器
p_shared_vec(vec, t1);
p_shared_vec(vec, t2);
p_shared_vec(vec, t3);
}
void test08()
{
test07();
//检查容器中的内容
for (auto it = vec.begin(); it!= vec.end(); it++)
{
cout << it->use_count() << endl;
}
//清空容器
vec.clear();
for (auto it = vec.begin(); it!= vec.end(); it++)
{
cout << it->use_count() << endl;
}
}
(2)独占智能指针unique_ptr
独占即不允许其他管理者存在,只能有一个唯一的内存管理者。它禁止拷贝构造和赋值函数。 独占指针的成员函数: 构造:unique_ptr up(要管理的内存地址); 辅助函数:make_unique(初始化的值); reset():重置,解除对内存的管理。 reset(要管理的新内存地址):重置后,去管理新内存 get():返回裸指针
//独占智能指针unique_ptr:
void test09()
{
//构造
unique_ptr<Test> up1(new Test(10));
unique_ptr<Test> up2 = make_unique<Test>(8);
up1->print();
up2->print();
(*up1).print();
//get方法
Test* tp = up1.get();
tp->print();
//重置reset
up1.reset();
up2.reset(new Test(100));
up2->print();
//独占指针禁用了拷贝构造和赋值
//但是它支持移动语义
unique_ptr<Test> up3(move(up2));//这里调用了移动构造函数
up3->print();//100
}
弱共享智能指针weak_ptr
弱共享不是真正的共享,弱是因为这种智能指针并不能真正管理内存资源,它只起到了监视的作用,它可以监视某个内存当前的状态,比如有几个管理者,比如有没有被释放,所以可以看作共享智能指针的一个助手。所以这种指针不需要重载指针的运算符,并且它不会造成引用计数的改变。 弱共享智能指针的成员函数: 构造:weak_ptr wp(要监视的共享指针对象); 拷贝构造:weak_ptr wp(另一个弱共享指针);从而实现共同监视一块内存 operator=():也可以实现共同监视一块内存 use_count():获取所监视内存的引用计数 expired():判断所监视的内存是否被释放,返回bool值 lock():获取所监视内存的某个管理者 reset():重置它,不再监视资源
弱共享指针主要解决循环引用问题,只需要将两个类中任意一个类的共享智能指针替换成弱共享即可。
//弱共享智能指针weak_ptr:
void test10()
{
shared_ptr<int> sp(new int(10));//第一个管理者
cout << "sp:" << sp.use_count() << endl;//1
weak_ptr<int> wp1;
cout << "wp1:" << wp1.use_count() << endl;//0
weak_ptr<int> wp2(sp);//此时wp2监视sp管理的内存
cout << "wp2:" << wp2.use_count() << endl;//1
weak_ptr<int> wp3(sp);//wp3也监视了sp管理的内存
cout << "wp3:" << wp3.use_count() << endl;//1
//增加一个管理者
shared_ptr<int> sp1 = sp;//有两个管理者了
cout << "wp3:" << wp3.use_count() << endl;//2
//拷贝构造和赋值
weak_ptr<int> wp4(wp3);//wp4也加入监视
wp1 = wp4;//wp1也加入了
cout << "wp1:" << wp1.use_count() << endl;//2
cout << "wp4:" << wp4.use_count() << endl;//2
//查看监视内存的状态,有么有被释放
cout << wp3.expired() << endl;//0,代表
总结
1)共享智能指针用在所有权不明情况下,允许多个管理者同时存在,比如多个对象共享同一对象,跨多个函数共享对象,不确定最终由谁释放。 2)独占智能制造表示独占权,不允许有其他管理者。通常用于管理动态分配的内存(谁申请的谁释放)或资源(比如说系统资源) 3)弱共享用于需要观察资源是否存活但是不控制资源的情况下,可以解决循环引用问题。 4)如果你决定使用智能指针,那就在代码中都是用智能指针,不要跟裸指针一起混用。 5)C++没有垃圾回收机制,所以除了内存要自己管理,系统资源一样需要子管理。智能指针也可以回收系统资源,具体做法就是在删除器里实现对应资源的回收。 6)使用智能指针的时候,先构造出来,给它一个名字,再去使用,包括传参。 7)对于性能要求比较高的程序,不适合使用智能指针,因为会造性能的下降。 8)智能指针在多线程情况下运行的时候,独占指针是线程安全的,共享指针是不安全的,弱共享也是安全的。
循环引用问题
//解决循环引用问题
class B_break;//前向声明
class A_break
{
public:
shared_ptr<B_break> bsp;
~A_break() { cout << "A_break的析构" << endl; }
};
class B_break
{
public:
weak_ptr<A_break> asp;//这里改成了弱指针
//这样,A_break类对象就不会增加引用计数,引用计数仍然保持为1
//这样A_break对象就可以被正常析构,析构的时候就会把bsp对象析构掉,就会导致bsp管理的内存计数-1,变成1,然后b智能指针再次析构,引用计数变成0,
//最终B_break对象也可以被正常析构了,全释放了,没有内存泄露了
~B_break() { cout << "B_break的析构" << endl; }
};
void test11()
{
//循环引用问题
shared_ptr<A_break> a(new A_break);//a智能指针管理了A_break对象
cout << a.use_count() << endl;//1
shared_ptr<B_break> b(new B_break);//b智能指针管理了B_break对象
cout << b.use_count() << endl;//1
//下面让a和b相互引用,通过赋值,让a的属性bsp跟b进行共享,同b的属性asp跟a进行共享
a->bsp = b;
b->asp = a;
cout << a.use_count() << endl;//1
cout << b.use_count() << endl;//2
}
作业
/*定义一个水果基类,属性有产地,质量等级。方法有展示介绍方法。
定义不同的具体水果类比如香蕉苹果葡萄,继承自水果类,
自己的属性有名称,品种,价格,介绍描述(用动态申请空间保存)。
方法有展示介绍方法(直接展示介绍描述的属性内容即可)。
定义一个消费者类,自己的属性有姓名,年龄。方法有买水果的方法
(需要传入水果对象作为参数),买水果的方法还需要对买来的水果做介绍。
要求:实现动态多态,同时使用智能指针避免内存泄露。*/
//作业:使用智能指针和多态的练习
class Fruit
{
protected:
string m_cFrom;//产地
int m_nGrade;//质量等级
public:
Fruit(string from,int grade):m_cFrom(from),m_nGrade(grade){}
virtual void show() { cout << "产地:" << m_cFrom << ",质量等级:" << m_nGrade << endl; }
};
//子类,香蕉
class Banana :public Fruit
{
string m_cName;//名称
string m_cBreed;//品种
double m_dPrice;//价格
string* m_cDesc = nullptr;//介绍描述
public:
Banana(string from, int grade, string name, string breed, double price, string desc) :Fruit(from, grade)
{
if (m_cDesc==nullptr)
{
m_cDesc = new string(desc);
}
m_cName = name;
m_cBreed = breed;
m_dPrice = price;
}
~Banana()
{
//析构函数负责释放堆内存
if (m_cDesc!=nullptr)
{
delete m_cDesc;
}
m_cDesc = nullptr;
}
virtual void show()
{
Fruit::show();
cout << "名称:" << m_cName << ",品种:" << m_cBreed << ",价格:" << m_dPrice << ",描述:" << *m_cDesc << endl;
}
};
//子类,苹果
class Apple :public Fruit
{
string m_cName;//名称
string m_cBreed;//品种
double m_dPrice;//价格
string* m_cDesc = nullptr;//介绍描述
public:
Apple(string from, int grade, string name, string breed, double price, string desc) :Fruit(from, grade)
{
if (m_cDesc == nullptr)
{
m_cDesc = new string(desc);
}
m_cName = name;
m_cBreed = breed;
m_dPrice = price;
}
~Apple()
{
//析构函数负责释放堆内存
if (m_cDesc != nullptr)
{
delete m_cDesc;
}
m_cDesc = nullptr;
}
virtual void show()
{
Fruit::show();
cout << "名称:" << m_cName << ",品种:" << m_cBreed << ",价格:" << m_dPrice << ",描述:" << *m_cDesc << endl;
}
};
//消费者类
class Consumer
{
string m_cName;//姓名
int m_nAge;//年龄
public:
Consumer(string name,int age):m_cName(name),m_nAge(age){}
void buy(Fruit* f)//这里智能传入父类指针,为了实现多态
{
cout << m_cName << "买了一些水果:" << endl;
f->show();
}
};
void test12()
{
//使用智能指针管理对象
unique_ptr<Fruit> up1(new Apple("陕西洛川", 1, "苹果", "红富士", 6.9, "洛川红富士是知名品牌,特点是个头大、颜色粉红、有条状花纹、水分大、甜度高"));
unique_ptr<Fruit> up2(new Banana("泰国", 1, "香蕉", "泰国品种", 5.5, "泰国香蕉香味足,个头大,易保存,老少皆宜"));
unique_ptr<Consumer> up_consumer(new Consumer("张三", 23));
up_consumer->buy(up1.get());
up_consumer->buy(up2.get());
}