手动管理内存,使用new、delete与C++中最常见的BUG——内存泄漏息息相关。尽管都知道使用new申请一块内存都应手动使用delete释放这块内存,但是随着代码复杂度的提升,一个指针或许会在多个函数中频繁传递,很多时候会使程序员犯错误,忘记释放内存,或者不知道应该在哪里释放内存。
C++中也提供了一种方法——智能指针,它可以自动地释放对象所占用的内存,并避免常见的内存泄漏和悬挂指针等问题。
不过,智能指针也有其限制和潜在的开销,因此在使用时需要根据具体情况进行权衡和选择。
C++标准库里面提供了两种主要的智能指针类型:std::shared_ptr 和 std::unique_ptr。
std::shared_ptr | 一种共享拥有权的智能指针 |
std::unique_ptr | 一种独占所有权的智能指针 |
当使用智能指针时需要引用<memory>标准库头文件。
#include <memory>
铺垫
先看下面代码:
void func(){
int a = 100; //栈内存,他的内存是由编译器自动分配的
int* p = &a; //p存储了a的地址
}
如注释所言,函数中 a 的内存是由编译器自动分配的,在函数结束时,编译器会自动释放 a 的内存。
与之相对的,我们也可以用new这个关键字自己动态请求一些内存。
void func(){
int *p = new int;
}
上述代码就动态请求一个int大小的内存。用new请求的内存,我们要用delete手动释放,不然,它是不会自动释放的,如下:
void func(){
int *p = new int;
delete p;
}
这里的delete是释放了p指向的内存,不是删除了p本身,在删除之后还可以把p指向别的地方。如下:
void func(){
int a = 100;
int *p = new int;
delete p;
p = &a;
}
new不仅可以用来请求单个内存,还可以用来请求用多个元素组成的数组,只需要加上 [] 和元素的个数就可以了。如下:
void func(){
int *p = new int[100];
delete[] p;
}
如果用new的时候请求了多个元素,那么在delete的时候也应该加上 [] ,不过delete的方括号不需要元素的个数,因为编译器会帮你记下来,你创建了多少个元素。
当调用一个函数,其中局部变量就分配在栈内存上面,函数调用结束局部变量就会自动释放,这个过程由编译器自动控制。堆内存用于动态分配内存,动态分配的意思就是,需要程序员手动去请求这块内存,使用完之后需要程序员手动释放,不然这块内存就会一直存在,造成内存泄漏。
如果在函数里面动态请求了一块内存,函数结束之后也没有手动释放掉,那么这块内存就会一直存在,但是别人也无法访问这块内存,因为唯一知道这块内存位置的指针p已经随着函数执行完毕被释放掉了,就会造成资源的浪费。久而久之,内存资源就会被完全的榨干,就没有内存可以使用了,造成系统的崩溃。
注意:
对于一个指针不要删两次。undefined behavior。
int *p = new int; delete p; delete p; //错误
共享智能指针
顾名思义,共享智能指针std::shared_ptr 是一种共享拥有权的智能指针,允许多个指针共同拥有同一个对象。它跟踪有多少个 shared_ptr 实例指向同一个对象,并在最后一个 shared_ptr被销毁时释放该对象。
std::shared_ptr
的使用方式如下:
-
创建
std::shared_ptr
对象并初始化:std::shared_ptr<Type> ptr = std::make_shared<Type>(args);
这将创建一个指向类型为
Type
的对象的shared_ptr
,并使用args
参数初始化对象。 -
使用
std::shared_ptr
指针:- 通过
->
操作符访问对象的成员:ptr->memberFunction(); ptr->memberVariable = value;
- 通过
*
操作符解引用指针并访问对象的成员:(*ptr).memberFunction(); (*ptr).memberVariable = value;
- 通过
-
共享拥有权:
- 将
std::shared_ptr
赋值给其他std::shared_ptr
对象,实现共享拥有权:std::shared_ptr<Type> ptr2 = ptr;
- 在函数间传递
std::shared_ptr
作为参数,共享拥有权:void someFunction(std::shared_ptr<Type> ptr);
- 将
-
手动释放对象:
- 如果不再需要共享对象,可以将
std::shared_ptr
重置为nullptr
,以释放对象:ptr.reset();
- 如果不再需要共享对象,可以将
请注意,当最后一个std::shared_ptr
指向对象时,对象将被自动释放。使用std::shared_ptr
时,避免循环引用,以免导致内存泄漏。
独占智能指针
std::unique_ptr
是一种独占拥有权的智能指针,它提供了以下使用方式:
-
创建
std::unique_ptr
对象并初始化:std::unique_ptr<Type> ptr = std::make_unique<Type>(args);
这将创建一个指向类型为
Type
的对象的unique_ptr
,并使用args
参数初始化对象。 -
使用
std::unique_ptr
指针:- 通过
->
操作符访问对象的成员:ptr->memberFunction(); ptr->memberVariable = value;
- 通过
*
操作符解引用指针并访问对象的成员:(*ptr).memberFunction(); (*ptr).memberVariable = value;
- 通过
-
转移拥有权:
- 通过将
std::unique_ptr
赋值给其他std::unique_ptr
来转移拥有权:std::unique_ptr<Type> ptr2 = std::move(ptr);
- 将
std::unique_ptr
作为返回值返回,从而转移拥有权:std::unique_ptr<Type> someFunction();
请注意,一旦拥有权转移给其他
std::unique_ptr
,原始指针将变为nullptr
。 - 通过将
-
手动释放对象:
- 如果不再需要对象,可以通过将
std::unique_ptr
赋值为nullptr
来手动释放对象并释放内存:ptr.reset();
std::unique_ptr
离开其作用域时,它会自动释放对象。
- 如果不再需要对象,可以通过将