c++动态内存智能指针及weak_ptr用法的理解

静态内存

1、静态内存:用来保存局部static对象(第一次经过时初始化直到程序终止才销毁,贯穿函数调用及之后的时间)、类static数据成员(与类本身相关与类对象无关,不能为const,不包含this)以及定义在任何函数之外的变量。

2、栈内存:用来保存定义在函数内的非static对象。

3、对于1和2,由编译器自动创建和销毁。

智能指针shared_ptr

4、为了避免使用动态指针带来的风险(内存泄漏或者产生引用非法内存的指针),推荐使用智能指针。

5、shared_ptr类 shared_ptr允许多个指针指向同一个对象,声明方式类似vector。

6、最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

7、几种使用make_shared方式

//指向一个值为42的int的shared_ptr
shared_ptr<int> p1 = make_shared<int> (42);
//指向一个值为”9999999999“的string
shared_ptr<int> p2 = make_shared<string>(10, "9");
//指向一个值初始化的int,即,值为0
shared_[tr<int> p3 = make_shared<int>();
//指向一个动态分配的空vector
auto p4 = make_shared<vector<string>>();

8、我们可以认为share_ptr有一个关联的计数器,通常称作引用计数,来确保当其为0时释放空间。当我们进行拷贝或者赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。

9、造成shared_ptr的计数器递增的情况:(1)当用一个shared_ptr初始化另一个shared_ptr(2)将它作为参数传递给一个函数(3)作为函数的返回值

10、造成shared_ptr递减的情况:(1)给原有的shared_ptr赋予新值(2)shared_ptr被销毁,比如说一个局部的shared_ptr离开其作用域。

11、当有多个shared_ptr指向同一块动态内存时,即使其中某个被销毁也不会释放此内存,直到最后一个shared_ptr被销毁时才释放内存。因此我们要保证shared_ptr在无用之后不再保留。比如说,你将shared_ptr存放于某一个容器中,而后不再需要全部元素,而只使用一部分元素,要记得用erase删除不再需要的元素。

12、程序使用动态内存出于以下三个原因之一:(1)程序不知道自己需要使用多少对象eg:容器类(2)程序不知道所需对象的准确类型(3)程序需要在多个对象间共享数据。

直接管理内存

13、我们也可以采用new和delete来直接管理动态内存,但风险比较大,且没有智能指针所提供的相关操作。

14、new无法为其分配的对象命名,而是返回值一个指向该对象的指针

int *pi = new int;
int *pt = new int(); //值初始化为0
int *p = new int(42);
string s = new string; //类类型对象将使用默认构造函数进行初始化,s为空string
vector<int> *pv = new vector<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

默认情况下,动态分配的对象时默认初始化的(内置类型定义于任何函数体外的量初始化为0,定义在函数体内的内置类型的值为定义),这意味着内置类型或组合类型的对象的值是未定义的,因此我们可以对其采用值初始化的方式来初始化一个动态分配的对象。

15、一个动态分配的const必须进行初始化。

const int *pci = new const int(1024);

17、内存耗尽:如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常:

int *p = new (nothrow) int; //如果分配失败,new返回一个空指针

该形式的new称为定位new

18、delete包含两个操作(1)销毁给定的指针指向的对象(2)释放对应的内存

19、以下delete行为的后果是未定义的

int i, *pi1 = &i;
double *pd = new double(33), *pd2 = pd;
delete i; //错误 i 不是指针
delete pi1; //未定义:pi1指向一个局部变量
delete pd;
delete pd2; // pd2指向的内存已经被释放 

20、常见的三个错误(1)忘记delete内存,也就是内存泄漏(2)使用已释放的对象(3)同一块内存连续释放两次。

21、空悬指针:指向一块曾经保存数据对象但现在已经无效的内存的指针。常见于delete一个指针后,很多机器仍保留着(已经释放的)动态内存的地址。我们可以在delete之后将nullptr赋给该指针,显示的指出该指针不指向任何对象。

shared_ptr与new结合

22、必须采用直接初始化形式用new来初始化shared_ptr,因为接受指针参数的智能指针构造函数为exclicit,必须显示的声明。并且用于初始化智能指针的普通指针必须指向一个动态内存。

shared_ptr<int> p1 (new int(1024));

22、部分操作列举:

shared_ptr<T> p(q,d); //p接管了内置指针q所指向的对象的所有权。q必须能转化为T*类型。
                     //p将使用可调用对象d来代替delete

//若p是唯一指向其对象的shared_ptr,reset会释放此对象。
//若传递了可选的参数内置指针q,会另p指向q,否则会将p置空。
//若还传递了参数d,将会调用d而不是delete来释放q
p.reset();
p.reset(q);
p.reset(q,d);

23、尽量不要混用new和shared_ptr,因为shared_ptr的析构仅限于shared_ptr之间,这也是推荐使用make_sahred而不是new的原因。

24、当混用了new和shared_ptr时,一旦我们将一个shared_ptr绑定到一个普通指针上,我们就将内存的管理责任交给了shared_ptr,之后我们就不应该再使用内置指针来访为shared_ptr指向的对象了。

25、不要用get初始化另一个智能指针或为智能指针赋值。(get仅被用于我们需要向不适用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete此指针)

26、reset经常与unique一起使用来控制多个shared_ptr共享的对象。用来判定我们是否是当前对象仅有的用户,若不是,在改变之前需要制作一份新的拷贝。

智能指针和异常

27、使用智能指针的一些基本规范:
(1)不使用相同的内置指针初始化或reset多个智能指针。
(2)不delete get() 返回的指针。
(3)不使用get() 初始化或reset另一个智能指针。
(4)如果使用了get() 返回的指针,记住当最后一个对应的智能指针销毁后,这个返回的指针也无效了。
(5)如果智能指针管理的资源不是new分配的内存,记得传递给它一个删除器。

unique_ptr

28、当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。必须采用直接初始化形式。不支持普通的拷贝或者赋值操作。

29、一些基本操作

u.release(); //u放弃对指针的控制权,返回值真,并将u置空
u.reset();//释放u指向的对象
u.reset(q);//如果提供了内置指针q,令u指向这个对象,否则将u置空
u.reset(nullptr);

//我们可以用release和reset来将指针的所有权从一个(非const)unique_ptr转移给另一个unique
unique_ptr<string> p2(p1.release());//release将p1置空,将所有权转移给p2
unique_ptr<string> p3(new string("Text"));
p2.reset(p3.release());//将所有权从p3转移给p2,并且释放了p2原来指向的内存。

30、例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr

unique_ptr<int> clone(int p){
          return unique_ptr<int>(new int(p));
}

//or
unique_ptr<int> clone(int p){
          unique_ptr<int> ret(new int(p));
          return ret;
}

weak_ptr

31、weak_ptr指向由一个shared_ptr管理的对象,不控制对象生存期,不会改变shared_ptr的引用计数,当最后一个shared_ptr被销毁,对象就会被释放。

32、部分weak_ptr操作

w.use_count();//与w共享对象的shared_ptr的数量
w.expired();//若w.use_count返回0,返回ture,否则返回false
w.lock();//如果expired返回true,返回一个空shared_ptr,否则返回一个指向w对象的shared_ptr

33、weak_ptr解决了在循环引用中的内存泄露的情况,以及悬空指针的情况。

//悬空指针
shared_ptr<int> sptr(new int(10));  
weak_ptr<int> wptr1 = sptr;   
sptr.reset(new int(20));    
if(auto tmp = wptr1.lock())  //lock()返回shared_ptr,非空则输出
     cout << *tmp << '\n';
else
     cout << "expired!" << '\n';

循环引用:
c++动态内存智能指针及weak_ptr用法的理解_第1张图片
虚线代表weak_ptr,实线代表shared_ptr
当我们函数运行快结束时,此使指向B的shared_ptr有两个,指向A的有一个,因此在函数结束时,指向A的引用计数-- ,变为0,正常释放对象A,同时,A指向B的shared_ptr也被释放,此时,B的引用计数为1-- ,于是对象B也正常释放。
假设B指向A的也为shared_ptr,我们可以看到在函数即将结束时,两个对象引用计数都为2,无法正常释放内存,因此也就造成了内存泄漏。

你可能感兴趣的:(c++语言学习)