内存泄漏: 当我们动态地申请了一片内存以后,在程序中没有主动去释放它的时候,导致他在程序中再也访问不到了。所谓泄露的内存是指:进程中,属于进程,但是进程却访问不到的那片内存。
智能指针的自动析构功能能很好的解决内存泄漏问题。但是凡事都有例外,例如遇到环形引用的情况:
环形引用:

示例代码:
#include<iostream>
#include<memory>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
~A() {
cout << "destructor" << endl;
}
shared_ptr<A> ptr;
};
int main() {
shared_ptr<A> p = make_shared<A>(); //p指针指向了一个A1对象,A1对象里有一个指针指向了q指针
shared_ptr<A> q = make_shared<A>(); //q指针指向了一个A2对象,A2对象里有一个指针指向了p指针
p->ptr = q;
q->ptr = p;
cout << p.use_count() << endl;
cout << q->ptr.use_count() << endl;
p = nullptr;
cout << q->ptr.use_count() << endl;
cout << "end main" << endl;
return 0;
}
输出结果:
default constructor
default constructor
2
2
1
end main
可以发现:因为A2里的ptr对象还指着A1,所以即便p指针不再指向A1对象了,也不会触发自动析构函数把A1对象给析构掉。
这显然不是我们的原意,因为一旦q对象也指向了空地址,那么A1和A2两个对象因为互指的原因依然不会被析构。但是进程现在已经无法再通过指针找到这两个对象的内存,于是乎将造成内存泄漏。
模拟实现一个shared_ptr:
- 指向A类对象;
- 引用计数的设计
#include<iostream>
using namespace std;
class A {
public:
A(int x = 3, int y = 4) : x(x), y(y) {
cout << "default constructor" << endl;
}
int x, y;
~A() {
cout << "destructor" << endl;
}
};
ostream &operator<<(ostream &out, const A &a) {
out << "class A : "<< a.x << "," << a.y;
return out;
}
class shared_ptr {
public:
// TODO
};
shared_ptr make_shared() {
// TODO
}
int main() {
shared_ptr p = make_shared(); //RVO返回值优化
p->x = 3, p->y = 4;
cout << *p << endl;
cout << p.use_count() << endl;
shared_ptr q = p;
cout << p.use_count() << endl;
cout << q.use_count() << endl;
p = nullptr;
cout << q.use_count() << endl;
q = nullptr;
return 0;
}
关键代码的实现:
// 封装一个指针类
class ptr_data {
public:
ptr_data(A *ptr, int *cnt = nullptr) : ptr(ptr), cnt(cnt) {
if (this->cnt == nullptr) this->cnt = new int(1); //这里千万不要忘了加this
}
void increase_one() {
*cnt += 1;
return ;
}
void decrease_one() {
*cnt -= 1;
if (*cnt == 0) delete ptr;
}
bool operator==(const ptr_data &p) {
return ptr == p.ptr && cnt == p.cnt;
}
~ptr_data() {
if (*cnt == 0) delete cnt;
}
A *ptr;
int *cnt;
};
// 智能指针主要实现拷贝构造、赋值运算、引用计数、指针的常见功能。
class shared_ptr {
public:
shared_ptr(A *ptr) : p(ptr) {}
shared_ptr(const shared_ptr &p) : p(p.p) {
this->p.increase_one();
}
shared_ptr &operator=(const shared_ptr &obj) {
cout << "copy constructor" << endl;
if (p == obj.p) return *this;
p.decrease_one();
p = obj.p;
p.increase_one();
return *this;
}
~shared_ptr() {
p.decrease_one();
}
int use_count() { return *p.cnt; }
A *operator->() { return p.ptr; }
A &operator*() { return *p.ptr; }
private:
ptr_data p;
};
shared_ptr make_shared() {
return shared_ptr(new A());
}
每日问答聊天机器人:
问题1: c++中delete函数的内在机制是怎么样的?
ChatGPT: c++ 中的 delete 函数是用来释放动态分配的内存的。它有两种形式:
delete p:用于释放单个对象所占用的内存。
delete[] p:用于释放数组对象所占用的内存。
在释放内存之前,delete 会调用对象的析构函数,以便清理对象占用的资源。
delete 的内在机制是通过调用 operator delete 来实现的。operator delete 是一个函数指针,指向用于释放内存的函数。operator delete 的默认实现是系统函数,但是可以被用户自定义。
注意:在使用 delete 释放内存之前,应该确保这块内存是通过 new 运算符动态分配的。否则,可能会出现未定义的行为
问题2: 如果析构函数里什么都没做,对象的内存还会被释放掉吗?
ChatGPT: 是的,即使析构函数中没有任何代码,对象的内存也会被释放掉。析构函数的主要作用是在对象被销毁之前,释放它所占用的资源(如打开的文件、动态分配的内存等)。如果析构函数中没有任何代码,则表示该对象没有任何资源需要释放,所以没有任何影响。
问题3: 将对象赋值为nullptr,系统会自动调用对象的析构函数吗?
ChatGPT: 是的,将对象的指针赋值为 nullptr 会导致系统自动调用该对象的析构函数。如果在析构函数中释放了动态分配的内存或资源,那么在将对象指针赋值为 nullptr 后,这些内存和资源就会被释放。
例如,假设有一个类 MyClass,它的析构函数中释放了动态分配的内存:
class MyClass
{
public:
MyClass() { /* ... */ }
~MyClass() { delete[] m_ptr; }
// ...
private:
int* m_ptr;
};
int main()
{
MyClass* obj = new MyClass;
// ...
obj = nullptr; // 调用析构函数并释放动态分配的内存
}
在上面的代码中,将对象指针赋值为 nullptr 后,系统会自动调用该对象的析构函数,并释放动态分配的内存。