一、动态内存
在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。
C++定义指针变量什么时候需要手动释放?
int*, char* ,这些定义是局部变量,存在于栈上,比如int *p;p在栈上,而且p的值也是栈的一个地址。但是当int *p = new int ;这时候,p这个变量是在栈上的。但是p的值是一个地址,这个地址是堆上的一个地址。如果不delete p;那么,这个地址会一直被占用着,不能被其他的对象所使用,所以我们用完这个地址,要把这个地址释放掉。因此栈的空间会自动释放,而堆里的空间必须手动释放。
二、智能指针
2.1 智能指针简介
智能指针是存储动态分配(堆)对象指针的类,用于生存周期控制,能够确保在离开指针所在作用域时,自动正确地销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数,每使用一次,内部的引用计数加1,每析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
c++ 11 提供了3种智能指针:std::shared_ptr、std::unique_ptr、std::weak_ptr ,使用时引用头文件 <memory>。
- shared_ptr允许多个指针指向同一个对象
- unique_ptr则“独占”所指向的对象
- weak_ptr,它是一种弱引用,指向shared_ptr所管理的对象
2. 2 智能指针作用
因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
2.3 shared_ptr 共享的智能指针
std::shared_ptr 使用引用计数,每一个shared_ptr 的拷贝都指向相同的内存。在最后一个shared_ptr 析构时,内存才会被释放。
每个 shared_ptr 对象在内部指向两个内存位置:
1、指向对象的指针。
2、用于控制引用计数数据的指针。
共享所有权如何在参考计数的帮助下工作:
1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1;
2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存
多个 shared_ptr 对象可以共同托管一个指针 p,当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行delete p
。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
int i;
A(int n):i(n) { };
~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
shared_ptr<A> sp2(sp1); //A(2)同时交由sp2托管
shared_ptr<A> sp3;
sp3 = sp2; //A(2)同时交由sp3托管
cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
A * p = sp3.get(); // get返回托管的指针,p 指向 A(2)
cout << p->i << endl; //输出 2
sp1.reset(new A(3)); // reset导致托管新的指针, 此时sp1托管A(3)
sp2.reset(new A(4)); // sp2托管A(4)
cout << sp1->i << endl; //输出 3
sp3.reset(new A(5)); // sp3托管A(5),A(2)无人托管,被delete
cout << "end" << endl;
return 0;
}
输出结果:
2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed
2.3.1 智能指针的初始化方法
(1)构造函数初始化
std::shared_ptr<int> pointer(new int(1));//构造函数初始化智能指针
std::shared_ptr<int> pointer1 = pointer;//智能指针给智能指针赋值
std::cout << "pointer = " << *pointer << std::endl;
std::cout << "pointer1 = " << *pointer1;
(2)std::make_shared 初始化
std::shared_ptr<int> p3 = std::make_shared<int>(1);
std::cout << "p3 = " << *p3 << std::endl;
(3)reset 初始化
使用reset 方法初始化智能指针
std::shared_ptr<int> pointer;
pointer.reset(new int(1));
2.3.2 获取智能指针
当需要获取智能指针时,可以通过get方法来返回原始指针。
std::shared_ptr<int> p3 = std::make_shared<int>(1);
std::cout << "p3 = " << *p3 << std::endl;
int *ptr = p3.get();
std::cout << "ptr = " << *ptr << std::endl;
使用智能指针开辟空间
shared_ptr<char> queryResult(new char[300], [](char* data) { delete[] data; });
char *respData = queryResult.get();
2.3 .3 指定删除器
智能指针初始化可以指定删除器。
如下所示,当p 的引用计数为0时,自动调用删除器DeleteIntPtr 来释放对象的内存。
void DeleteIntPtr(int* p)
{
delete p;
std::cout << "delete p " << std::endl;
}
int main()
{
std::shared_ptr<int> pointer(new int(1),DeleteIntPtr);
}
也可以用 lambda 表达式的形式
std::shared_ptr<int> pointer1(new int(1), [](int* p) {delete p; });
2.3.4 错误用法
错误用法:
(1)不能将一个原始的指针直接赋值给一个智能指针
std::shared_ptr<int> p = new int(1);//错误用法
(2) 不能用一个原始指针初始化多个shared_ptr
int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);//错误用法,不能用一个原始指针初始化化多个 shared_ptr
(3)要避免循环引用,循环引用会导致内存泄露
struct A;
struct B;
struct A
{
std::shared_ptr<B> bptr;
~A(){ cout << "A is deleted!" << endl; }
};
struct B
{
std::shared_ptr<A> aptr;
~B(){ cout << "B is deleted!" << endl; }
};
int main()
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
return 0;
}
循环引用导致 ap和bp 的引用计数为2,在离开作用域之后,ap和bp 的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生了内存泄露。
2.3.5 自己编写一个智能指针
2.3.6 判断智能指针是否非空
访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。
2.4 unique_ptr 独占的智能指针
2.4.1 unique_ptr 基本概念
unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr 赋值给另外一个unique_ptr。
可以通过std::move 来转移其他的unique_ptr 。
std::unique_ptr<int> myPtr(new int(1));//okey
使用std::move 转移,如下所示,转移之后 myPtr 不能指向原来的对象。
std::unique_ptr<int> myPtr(new int(1));//okey
std::unique_ptr<int> myPtr1 = std::move(myPtr);//使用std::move 来转移到其他的unique_ptr
错误用法:
std::unique_ptr<int> myPtr1 = myPtr;//错误用法,unique_ptr 智能指针不能赋值给另外一个 unique_ptr
2.4.2 share_ptr 和 unique_ptr 的选择:
如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用share_ptr。
2.5 weak_ptr 弱引用的智能指针
弱引用指针weak_ptr 是用来监视shared_ptr 的,不会使引用计数加1,它也不管理shread_ptr 内部的指针,主要用来监视shared_ptr 的生命周期。
(1)通过use_count() 来获得当前观测资源的引用计数。
(2)通过expired() 方法来判断所观测的资源是否已经被释放。
(3)通过lock 方法来获取所监视的shared_ptr。
参考:
【1】C++11 - std::shared_ptr初始化的几种方式:C++11 - std::shared_ptr初始化的几种方式_HW140701的博客-CSDN博客_shared_ptr初始化
【2】C++ 智能指针 shared_ptr 详解与示例: c++11智能指针(一) shared_ptr - 简书
【3】C++11 shared_ptr(智能指针)详解: C++11 shared_ptr(智能指针)详解
【4】std::shared_ptr: shared_ptr - C++ Reference