在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。
笔记借鉴了《c++11实用特性[c/c++项目开发必备技能]》课程
智能指针是存储指向动态分配(堆)对象指针的类
,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。
智能指针的核心实现技术是引用计数
,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
C++11中提供三种智能指针,使用这些智能指针时需要引用头文
< memory >
std::shared_ptr
:共享的智能指针std::unique_ptr
:独占的智能指针std::weak_ptr
:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。
一、 shared_ptr共享智能指针
共享智能指针是指多个智能指针可以同时管理同一块有效的内存
1.1 shared_ptr的初始化
共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数
、std::make_shared辅助函数
以及reset方法
。共享智能指针对象初始化完毕之后就指向了要管理的堆内存.
如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count
,函数原型如下:
// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
1.1.1 通过构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);
#include <iostream>
#include <memory> // 包含头文件
using namespace std;
int main(){
// 使用智能指针管理一块 int 型的堆内存
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; // 1
// 使用智能指针管理一块字符数组对应的堆内存
shared_ptr<char> ptr2(new char[12]);
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; // 1
// 创建智能指针对象, 不管理任何内存
shared_ptr<int> ptr3;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; // 0
// 创建智能指针对象, 初始化为空
shared_ptr<int> ptr4(nullptr);
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 0
return 0;
}
Tip:不要使用一个原始指针初始化多个shared_ptr
int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // error, 编译不会报错, 运行会出错
1.1.2 通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了。
#include <iostream>
#include <memory>
using namespace std;
int main(){
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; // 1
//调用拷贝构造函数
shared_ptr<int> ptr2(ptr1);
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; // 2
shared_ptr<int> ptr3 = ptr1;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; // 3
//调用移动构造函数
shared_ptr<int> ptr4(std::move(ptr1));
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 3
std::shared_ptr<int> ptr5 = std::move(ptr2);
cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl; // 3
//如果使用移动的方式初始智能指针对象,只是转让了内存的所有权
//管理内存的对象并不会增加,因此内存的引用计数不会变化。
return 0;
}
1.1.3 make_shared()
在 C++11 中,可以使用
make_shared
函数来创建共享指针,它是一个模板函数,可以接受任何类型的参数,并返回一个指向该类型对象的共享指针。它可以将控制块和对象一起分配在堆上,从而避免了两次内存分配,并减少了引用计数的内存开销,相对于直接使用new
或shared_ptr
的构造函数,make_shared
更为高效。
std::shared_ptr<T> make_shared< T >( Args&&... args );
- T 表示指向的类型,
- Args 是类型 T 的构造函数所需的参数列表,在调用时需要传递给构造函数。
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() {
}
~MyClass() {
}
void func() {
std::cout << "Hello world!" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
ptr->func();
return 0;
}
在上面的代码中,我们使用 make_shared
函数来创建一个指向 MyClass
类对象的共享指针。需要注意的是,make_shared
参数必须是完整类型,因此需要提前定义 MyClass
类。
接着,可以使用箭头运算符 ->
来调用 MyClass 中的成员函数。
make_shared
可以通过参数列表传递参数给对象的构造函数,例如:
std::shared_ptr<int> ptr = std::make_shared<int>(42);
-
当某个对象不再被任何指针引用时,
make_shared
会自动销毁该对象。如果对象同时还有其他资源(如文件或网络连接)需要释放,则需要在析构函数中处理。 -
如果程序使用了多线程,不能在多个线程中共同访问同一个
shared_ptr
对象,需要使用std::atomic<std::shared_ptr<T>>
或std::mutex
等机制来确保线程安全。 -
make_shared
函数可能会使得控制块和对象分配在同一块连续内存上,从而使得共享指针的内存占用更少。但是,如果使用make_shared
来创建一个较大的对象,可能会增加堆分配的消耗。
1.1.4 通过 reset方法初始化
共享智能指针类提供的std::shared_ptr::reset方法函数原型如下:
void reset() noexcept;
template< class Y >
void reset( Y* ptr );
template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );
template< class Y, class Deleter