动态内存管理常出现的两种问题:
1.忘记释放内存,造成内存泄漏
2.这块内存还有其他指针指向的情况下,就释放了它,会产生引用非法内存的指针,例如
- 如果类中有属性指向堆区,做赋值操作时会出现浅拷贝的问题
内存泄漏分类:
1.堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据需要分配,
- (malloc/calloc/realloc) / free 从堆中分配/释放的一块内存
- new/delete 从堆中分配/释放的一块内存
- new []/delete[] 从堆中分配/释放的一块内存
没有配套使用,就产生Heap leak
2.系统资源泄漏
指程序使用系统分配的资源,比如套接字,文件描述符,管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定
RAII [1](Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。(百度百科)
这种做法有两大好处:
- 不需要显示地释放资源
- 对象所有的资源在其生命期内始终保持有效
智能指针的原理:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为
#include <iostream>
using namespace std;
/*动态内存管理经常会出现两种问题:1.忘记释放内存,造成内存泄漏2.尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针智能指针可以更加容易(更加安全)的使用动态内存.它的行为类似常规指针,重要的区别是它自动释放所指向的对象内存泄漏:因为疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制.不仅自己无法使用这段内存,而且也无法再将这段内存分配给其他程序,因而造成了内存的浪费内存泄漏危害:长期运行的程序出现内存泄漏,影响很不好.如操作系统/后台服务等出现内存泄漏会导致响应越来越慢,最终卡死,宕机内存泄漏分类:1.堆内存泄漏(Heap leak)堆内存指的是程序执行中依据需要分配,(malloc/calloc/realloc) / (free) 从堆中分配/释放的一块内存 (new)/(delete) 从堆中分配/释放的一块内存 假设程序设计错误导致这块内存没有释放掉, 这部分空间再也无法被使用,也就产生Heap leak2.系统资源泄漏指程序使用系统分配的资源,比方套接字,文件描述符,管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存,文件句柄,网络连接,互斥量等等)的简单技术在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源.借此,我们实际上把管理一份资源的责任托管给了一个对象这种做法有两大好处:1.不需要显示地释放资源2.采用这种方式,对象所有的资源在其生命期内始终保持有效3.智能指针的原理*/
#include <iostream>
using namespace std;template<class T>
class SmartPtr {
public:SmartPtr(T* ptr) :m_ptr(ptr) {}~SmartPtr() {cout<<"delete->"<<m_ptr<<endl;// 释放资源delete m_ptr;}//指针可以通过->去访问所指空间中的内容(->重载)T* operator->() {return m_ptr;}// 指针可以解引用(*重载)T& operator*() {return *m_ptr;}
private:T* m_ptr;
};struct Person {string m_name;int m_age;
};int main() {// p在出作用域的前一刻会自动调用析构函数来释放资源// SmartPtr<int> p(new int); // delete->0x224d5a3d1c0// 对同一个资源进行了多次析构造成的SmartPtr<Person> person(new Person);person->m_name = "Tom";person->m_age = 18;cout<<"name: " <<(*person).m_name<<" age: "<<person->m_age<<endl;// 默认的拷贝构造函数就属于浅拷贝SmartPtr<Person> p1(person);// SmartPtr<Person> p1 = person;cout<<"name: " <<p1->m_name<<" age: "<<p1->m_age<<endl;return 0;
}
这里的SmartPtr<Person> p1(person);用到了默认拷贝构造函数
SmartPtr<Person> p1= person;用到了默认operator=
大致实现逻辑如下:
SmartPtr(const SmartPtr<T>& sp) :m_ptr(sp.m_ptr) {}
SmartPtr<T>& operator=(const SmartPtr<T>& sp) {if (this != &sp) {m_ptr = sp.m_ptr;}return *this;
}
- 故对同一个资源进行了多次析构会造成异常中断
一、auto_ptr
(1) 智能指针auto_ptr
- auto_ptr_test.cpp
#include <iostream>
#include <string>
#include <memory>
using namespace std;
// C++98版本的库中就提供了auto_ptr的智能指针
class Person {
public:Person(string name,int age):m_name(name),m_age(age){}~Person(){cout<<"~Person()"<<endl;}void setName(string name) {m_name = name;}void setAge(int age) {m_age = age;}void print() const{cout<<"name: "<<m_name<<", age: "<<m_age<<endl;}
private:string m_name;int m_age;
};int main() {std::auto_ptr<Person> ptr(new Person("Tom",12));ptr->print();// auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了std::auto_ptr<Person> copyPtr(ptr);copyPtr->print();copyPtr->setName("Jerry");copyPtr->setAge(8);copyPtr->print();// 这里ptr就悬空了// ptr->setName("heheda");return 0;
}
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
name: Tom, age: 12
name: Tom, age: 12
name: Jerry, age: 8
~Person()
PS D:\Work\c++\bin>
auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了
(2) auto_ptr的实现原理:管理权转移的思想
- AutoPtr_main.cpp
#include <iostream>
using namespace std;class Person {
public:Person(){ cout << "Person()" << endl; }~Person(){ cout << "~Person()" << endl; }string m_name;int m_age;
};// 管理权转移的思想
template<class T>
class AutoPtr {
public:AutoPtr(T* ptr = nullptr) : m_ptr(ptr) {}~AutoPtr() { if(m_ptr) delete m_ptr; }// 一旦发生拷贝,就把ap中资源转移到当前对象中,然后// 让ap与其所管理资源断开联系,这样就解决了一块空// 间被多个对象使用而造成程序崩溃问题AutoPtr(AutoPtr<T>& ap) : m_ptr(ap.m_ptr) { ap.m_ptr = nullptr; }AutoPtr<T>& operator=(AutoPtr<T>& ap) {// 检测是否为自己给自己赋值if (this != &ap) {// 释放当前对象中资源if(m_ptr) delete m_ptr;// 转移ap中资源到当前对象中m_ptr = ap.m_ptr;ap.m_ptr = nullptr;}return *this;}/*重载operator*和opertaor->,具有像指针一样的行为*/T* operator->() const { return m_ptr; }T& operator*() const { return *m_ptr; }
private:T* m_ptr;
};int main() {AutoPtr<Person> ap(new Person);// 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,// 导致ap对象悬空,再通过ap对象访问资源时就会出现问题AutoPtr<Person> copy(ap);// 这里访问资源时,就会出问题// ap->m_name = "Tom";return 0;
}
- 用auto_ptr的原理来修改前面的Smart_Ptr
修改拷贝构造函数和operator=函数
SmartPtr(SmartPtr<T>& sp) :m_ptr(sp.m_ptr) {sp.m_ptr = nullptr;}SmartPtr<T>& operator=(SmartPtr<T>& sp) {// 检测是否为自己给自己赋值if (this != &sp) {// 释放当前对象中资源if (m_ptr) delete m_ptr;// 转移sp中资源到当前对象中m_ptr = sp.m_ptr;sp.m_ptr = nullptr;}return *this;}
- SmartPtr.cpp
#include <iostream>
using namespace std;template<class T>
class SmartPtr {
public:SmartPtr(T* ptr) :m_ptr(ptr) {}~SmartPtr() {cout << "delete->" << m_ptr << endl;// 释放资源if(m_ptr) delete m_ptr;}SmartPtr(SmartPtr<T>& sp) :m_ptr(sp.m_ptr) {sp.m_ptr = nullptr;}SmartPtr<T>& operator=(SmartPtr<T>& sp) {// 检测是否为自己给自己赋值if (this != &sp) {// 释放当前对象中资源if (m_ptr) delete m_ptr;// 转移sp中资源到当前对象中m_ptr = sp.m_ptr;sp.m_ptr = nullptr;}return *this;}//指针可以通过->去访问所指空间中的内容(->重载)T* operator->() {return m_ptr;}// 指针可以解引用(*重载)T& operator*() {return *m_ptr;}
private:T* m_ptr;
};struct Person {string m_name;int m_age;
};int main() {SmartPtr<Person> person(new Person);person->m_name = "Tom";person->m_age = 18;cout << "name: " << (*person).m_name << " age: " << person->m_age << endl;// 默认的拷贝构造函数就属于浅拷贝SmartPtr<Person> p1 = person;cout << "name: " << p1->m_name << " age: " << p1->m_age << endl;// 导致person对象悬空,再通过person对象访问资源时就会出现问题//cout << "name: " << (*person).m_name << " age: " << person->m_age << endl; // 异常中断return 0;
}
二、unique_ptr
(1) 智能指针unique_ptr
- unique_ptr_test.cpp
// C++11中开始提供更靠谱的unique_ptr
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Person
{
public:Person(string name, int age) :m_name(name), m_age(age) { cout << "name: " << name << ", age: " << age << endl; }~Person() { cout << "~Person()" << endl; }string m_name;int m_age;
};
int main() {std::unique_ptr<Person> person(new Person("heheda", 12));// unique_ptr的设计思路非常的粗暴-防拷贝,也就是不让拷贝和赋值//unique_ptr<Person> copyPerson(person); // error// 下面的可以不看unique_ptr<Person> person2 = std::move(person);cout << "name: " << person2->m_name << ", age:" << person2->m_age << endl;//cout << "name: " << person->m_name << ", age:" << person->m_age << endl; // 异常return 0;
}
(2) unique_ptr的实现原理:简单粗暴的防拷贝,就是将拷贝构造函数和operator=函数设置为delete
- UniquePtr_main.cpp
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:Person(string name,int age):m_name(name),m_age(age){ cout<<"name: "<<name<<", age: "<<age<<endl;}~Person(){ cout << "~Person()" << endl; }string m_name;int m_age;
};
// unique_ptr的实现原理:简单粗暴的防拷贝
template<class T>
class UniquePtr {
public:UniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}~UniquePtr() { if(m_ptr) delete m_ptr; }T& operator*() const { return *m_ptr; }T* operator->() const { return m_ptr; }// C++11防拷贝的方式:deleteUniquePtr(UniquePtr<T> const&) = delete;UniquePtr<T>& operator=(UniquePtr<T> const&) = delete;
private:T* m_ptr;
};int main() {UniquePtr<Person> person(new Person("heheda",12));// unique_ptr的设计思路非常的粗暴-防拷贝,也就是不让拷贝和赋值// UniquePtr<Person> copyPerson(person); // errorreturn 0;
}
三、shared_ptr
(1) 智能指针 shared_ptr
shared_ptr是一种智能指针(smart pointer),作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting)。
一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。(百度百科)
- shared_ptr_test.cpp
// C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Person
{
public:Person(string name, int age) :m_name(name), m_age(age) { cout << "name: " << name << ", age: " << age << endl; }~Person() { cout << "~Person()" << endl; }string m_name;int m_age;
};
int main() {// shared_ptr 通过引用计数支持智能指针对象的拷贝shared_ptr<Person> sp(new Person("heheda", 12));shared_ptr<Person> copySp = sp;cout << "ref count:" << sp.use_count() << endl;cout << "ref count:" << copySp.use_count() << endl;return 0;
}
(2) shared_ptr的实现原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源
1.类似RAII的思路(普通构造创建资源、析构释放资源)
2.像指针一样使用(operator 、 operator->等运算符的重载)
3.能够拷贝(且必须是浅拷贝) (自己编写拷贝构造、赋值重载的函数)
4.计数器Count(计数器必须是同一份资源的计数器,而且是多个对象所共享的)
- 不能是普通int型变量,不然每个对象都会有不同的一个count
- 也不能是static型变量,否则所有资源都是共享的同一份计数器
- 只能用int* 类型的计数器
5.SharedPtr本身是线程安全的,所以在实现的过程中也必须上锁
#include <iostream>
#include <string>
#include <mutex>
using namespace std;class Person
{
public:Person(string name,int age):m_name(name),m_age(age){ cout<<"name: "<<name<<", age: "<<age<<endl;}~Person(){ cout << "~Person()" << endl; }string getName() const { return m_name; }int getAge() const { return m_age; }
private:string m_name;int m_age;
};template <class T>
class SharePtr
{
public:SharePtr(T *ptr = nullptr): m_ptr(ptr), m_pRefCount(new int(1)), m_mutex(new mutex){}// 拷贝构造函数SharePtr(const SharePtr<T> &sp): m_ptr(sp.m_ptr), m_pRefCount(sp.m_pRefCount), m_mutex(sp.m_mutex){// 增加引用计数addRefCount();}// operator=SharePtr<T>& operator=(const SharePtr<T> &sp) {// if(this != &sp)// 这样写防止 p2(p1) p1=p2 防止"自己赋值自己"if(m_ptr != sp.m_ptr) {// 先释放之前管理的资源release();m_ptr = sp.m_ptr;m_pRefCount = sp.m_pRefCount;m_mutex = sp.m_mutex;// 增加引用计数addRefCount();}return *this;}T* get() { return m_ptr; }int useCount() { return *m_pRefCount; }// operator 、 operator->等运算符的重载T* operator->(){ return m_ptr; }T& operator*(){ return *m_ptr; }~SharePtr() { release(); }
private:void release() {bool deleteflag = false;m_mutex->lock();if(--(*m_pRefCount) == 0) {cout << "delete:" << m_ptr << endl;// 释放资源delete m_ptr;delete m_pRefCount;deleteflag = true;}m_mutex->unlock();//表示m_ptr 和 m_pRefCount资源已经被释放,此时需要释放锁,否则产生资源泄漏if(deleteflag) delete m_mutex;}void addRefCount() {m_mutex->lock();++(*m_pRefCount);m_mutex->unlock();}
private:T *m_ptr; // 指向管理资源的指针int *m_pRefCount; // 指向引用计数的指针mutex *m_mutex; // 互斥锁,处理线程安全
};int main() {// shared_ptr 通过引用计数支持智能指针对象的拷贝SharePtr<Person> sp(new Person("heheda",12));SharePtr<Person> copySp = sp;cout << "ref count:" << sp.useCount() << endl;cout << "ref count:" << copySp.useCount() << endl;cout<<"name:"<<sp->getName()<<" age:"<<sp->getAge()<<endl;cout<<"name:"<<copySp->getName()<<" age:"<<copySp->getAge()<<endl;return 0;
}
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
name: heheda, age: 12
ref count:2
ref count:2
name:heheda age:12
name:heheda age:12
delete:0x21ef825f6a0
~Person()
PS D:\Work\c++\bin>
注意:智能指针是线程安全的,因为智能指针中引用计数++,--加锁了。但是智能指针管理的资源不是线程安全的,需要自己手动控制
(3)循环引用
// std::shared_ptr的循环引用
#include <iostream>
#include <string>
#include <memory>
using namespace std;struct ListNode {int m_data;shared_ptr<ListNode> m_prev;shared_ptr<ListNode> m_next;~ListNode() { cout<< "~ListNode()" << endl; }
};int main() {shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout<<node1.use_count() << endl; cout<<node2.use_count() << endl;node1->m_next = node2;node2->m_prev = node1;cout<<node1.use_count() << endl;cout<<node2.use_count() << endl;// 循环引用:node1和node2在生命周期结束后并没有去调用析构函数return 0;
}
- 循环引用:node1和node2在生命周期结束后并没有去调用析构函数,原因就是引用计数不为0,故导致资源无法释放
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
1
1
2
2
PS D:\Work\c++\bin>
四、weak_ptr
(1)weak_ptr_test.cpp
解决方案:在引用计数的场景下,把节点中的m_prev和m_next改成weak_ptr就可以了,因为weak_ptr类型的不会自增引用计数
#include <iostream>
#include <string>
#include <memory>
using namespace std;// 解决方案:在引用计数的场景下,把节点中的m_prev和m_next改成weak_ptr就可以了
struct ListNode {int m_data;weak_ptr<ListNode> m_prev;weak_ptr<ListNode> m_next;~ListNode() { cout<<"~ListNode()"<<endl;}
};int main() {shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout<<node1.use_count()<<endl; cout<<node2.use_count()<<endl;node1->m_next = node2;node2->m_prev = node1;cout<<node1.use_count()<<endl; cout<<node2.use_count()<<endl;return 0;
}
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
1
1
1
1
~ListNode()
~ListNode()
PS D:\Work\c++\bin>
原因就是weak_ptr类型的不会使得引用计数自增
(2)weak_ptr的实现原理,不让shared_ptr的引用计数增加就可以了
#include <iostream>
#include <string>
#include <memory>
using namespace std;template<class T>
class WeakPtr {
public:WeakPtr() : m_ptr(nullptr){}// 拷贝构造函数WeakPtr(const shared_ptr<T>& sp) : m_ptr(sp.get()){}// operator=WeakPtr& operator=(const shared_ptr<T>& sp) {m_ptr = sp.get();return *this;}T& operator*() { return *m_ptr; }// 重载*T* operator->() { return m_ptr; }// 重载->
private:T* m_ptr;
};struct ListNode {int m_data;WeakPtr<ListNode> m_prev;WeakPtr<ListNode> m_next;~ListNode() { cout<<"~ListNode()"<<endl;}
};// 原理:node1->m_next = node2; 和node2->m_prev = node1;
// 时weak_ptr的m_next 和 m_prev不会增加node1和node2的
// 引用计数
int main() {shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout<<node1.use_count()<<endl; cout<<node2.use_count()<<endl;node1->m_next = node2;node2->m_prev = node1;cout<<node1.use_count()<<endl; cout<<node2.use_count()<<endl;return 0;
}
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
1
1
1
1
~ListNode()
~ListNode()
PS D:\Work\c++\bin>
五、定制删除器
- delete只会调用一次对象的析构函数,而delete[]会调用后续所有对象的析构函数
// 定制删除器
#include <iostream>
#include <memory>
using namespace std;
class Object {
public:~Object() { cout << "~Object()" << endl; }
private:int m_data = 0;
};void test01() {unique_ptr<Object> p1(new Object);
}/*** 默认情况下:智能指针底层都是delete进行释放资源,* 这里new了10个Object,但底层delete时是delete p2,* 而不是delete[] p2*
*/
void test02() {unique_ptr<Object> p2(new Object[10]); // error
}// 定制删除器
template<class T>
struct DeleteArray{void operator()(T* p) const {cout << "delete[]" << endl;delete[] p;}
};
// 如果资源是new[],malloc,fopen出来的,需要用到定制删除器
void test03() {unique_ptr<Object, DeleteArray<Object>> p2(new Object[10]);
}int main() {test03();return 0;
}
PS D:\Work\c++\bin> ."D:/Work/c++/bin/app.exe"
delete[]
~Object()
~Object()
~Object()
~Object()
~Object()
~Object()
~Object()
~Object()
~Object()
~Object()
PS D:\Work\c++\bin>
参考文章:
C++智能指针之shared_Ptr的原理以及简单实现_shared ptr-CSDN博客https://blog.csdn.net/Arthur__Cui/article/details/131969612
C++中的浅拷贝、深拷贝、智能指针 - joannae - 博客园 (cnblogs.com)https://www.cnblogs.com/qionglouyuyu/p/4620714.html C++智能指针_智能指针是线程安全的吗-CSDN博客
https://blog.csdn.net/qq_56044032/article/details/125583967