要点汇总:1)   #9 - 基类的析构函数必须是virtual,否则可能导致析构调用链断层。
智能指针的使用:1)使用 普通指针/其他已存在的智能指针/其他已存在的普通指针,对当前创建的智能指针进行初始化。 (创建 指针 指向当前已有内存)
2)使用 make_shared 创建全新的内存区,然后创建一个全新的智能指针指向它。 (创建 内存空间 和 指针)
3)使用 智能指针的 reset 方法来进行 智能指针的 重定向 和 释放 (重定向 和 销毁) #1 ===========================================================================================================================
智能指针智能指针有两种: shared_ptr 和 unique_ptr
这两种指针都是模板类,因此原型为 xxx_ptr<T>
(!)注:智能指针是用来管理堆内存的,不是作为指针使用的
(!)智能指针是使用delete来释放内存,所以释放内存时的特性和delete一样
#2 ===========================================================================================================================
(!)首先明确一点,智能指针“仅仅”用来“动态分配内存”,而不是用来当做指针使用!!!!
因此:
int i = 100; //或者其他类,比如 classA a;
shared_ptr<int> sp = make_shared<int>(i);上述语句实际上是使用i的值作为新分配内存的初始值。sp并没有指向i,而是指向了新分配的堆,这个堆存放int型的数值100
上述语句不能算错误,但是需要理解 make_shared 动作实际上是取堆里申请了内存的。
(可以这样理解,make_shared相当于 new ,尖括号指定需要分配内存的类型名,小括号指定作为拷贝构造传递给
类型名的值,如果不指定,那么使用类型的默认构造) 同理:
classA *pa = new classA();
shared_ptr<classA> sp = make_shred<classA>(*pa); //使用classA的拷贝构造在堆里创建一个新的对象,而不是使用pa指向的对象 ====== EXAMPLE 1 =======
#include "stdafx.h"
#include <iostream>
#include <memory>
using namespace std;class A{
public:
A() = default;
~A(){ ; }
public:
int myint=10;
};int _tmain(int argc, _TCHAR* argv[])
{
A* pa = new A();
shared_ptr<A> sp = make_shared<A>(*pa);
sp->myint = 100; cout << pa->myint << endl; //pa = 10 ,值并没有改变,因为它是用 new 分配的堆
cout << sp->myint << endl; //sp = 100,值和pa不一样,因为它是用 make_shared 分配的堆 getchar();
return 0;
} 注:小括号里的内容必须与尖括号里类型的某个构造函数相匹配
====== EXAMPLE 2 ======
#include "stdafx.h"
#include <iostream>
#include <memory>
using namespace std;class A{
public:
A() = default;
~A(){ ; }
public:
int myint=10;
};int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr<A> sp = make_shared<A>(); //使用 A 的默认构造函数 cout << sp.use_count() << endl; //为1
shared_ptr<A> sp1 = sp; //增加了sp指向内存区的计数
cout << sp.use_count() << endl; //为2
cout << sp1.use_count() << endl; //为2 sp1 = NULL;
cout << sp.use_count() << endl; //为1
getchar();
//小结:可见智能共享指针的计数器是由系统自动管理的,具体内部实现可能是 静态数据类型,这样各个
// 实例操作的就是同一个数据,当然,可能会涉及到线程安全的问题,这时候就要加锁,这也伴随着
// 效率略低和死锁的风向 return 0;
}#3 ===========================================================================================================================
(!)在使用共享指针时,我们只需要操作指针对象,不要试图去直接操作指针指向的内容。因为当所有指针都销毁的时候,动态分配的内存会被
自动释放。 计数增加:
1)初始化一个shared_ptr
2)作为参数传递给一个函数
3)作为函数返回值计数减少:
1)给shared_ptr新指向对象,替换旧对象,那么就对象计数减少
2)shared_ptr调用析构函数,比如局部变量离开作用域等等 小结:shared_ptr将对对象的内存管理转变为对对指向对象指针的管理。而这个指针的获得仅能通过shared_make来实现。因此,从
某种意义上来说,更集中。
#4 ===========================================================================================================================
共享指针的应用场景:
1)程序不知道自己需要多少个对象???
2)程序不知道所需要对象的准确类型???
3)程序需要在多个对象之间共享数据,这有点像类的static成员变量,是所有类共享的。但是既然有static,为什么还要共享指针??? (!!!)使用动态内存的一个常见原因是:允许多个对象共享相同的状态。
(!!!)为什么不做成static的?
主要因为static数据存放在.bss区,无法手动释放,在程序运行时就载入,而这部分的可用空间是有限而狭小的。如果我们想要使用堆栈这样
宽敞的空间来模拟static成员,此时就可以使用共享指针管理的动态内存(堆)来实现,实现一个仅在大家都不在需要时才会释放的堆区域。 #5 ===========================================================================================================================
引用计数法的内部实现:
1)这个引用计数器保存在某个内部类型中,而这个内部类型对象在shared_ptr第一次构造时以指针的形式保存在shared_ptr中
2)shared_ptr重载了赋值运算符,在赋值和拷贝另一个shared_ptr时,这个指针被另一个shared_ptr共享
3)在引用计数归0时,这个内部类型指针与shared_ptr管理的资源一起释放
* 4)此外,为了保证线程安全,引用计数器的加1和减1都是原子操作,它保证了shared_ptr由多个线程共享时不会爆掉
#6 ===========================================================================================================================
一个典型的场景:
class A{
public:
vector<classB> m_B_list; //非常庞大的一个列表(实际这里可以使vector指针,这里只是做一个描述)}
如果A有N多个实例,问题就出现了,每个A实例都要有一个m_B_list,首先很吃内存,其次各个A实例之间需要实施互相同步数据,从而保证这个
vector对于所有A来说,都是一样的。这是很棘手和难处理的:
1)可以让vector是static的,但是这样比较占用static区域,而且无法释放 (不可选)
2)可以简单粗暴,让所有A在更改vector之后,通知其他A跟新自己的vector,这样既 占内存,又 占CPU (不可选)
3)创建一个command管理类,让command管理类来存放vector,并提供增删改查接口给所有A使用,这样保证了效率,一定程度上节约了内存。
但是,需要新增一个类,而这个类的存在感很低,因为它只是来做一个中转 (不是最优解)
4)不创建command管理类,但是在外部区域创建vector,然后让A都能访问,这样有风险,如果某个A出于“某种原因”把vector给删掉了。
那么其他A在访问的时候将访问到空指针,或者直接崩溃。这种情况特别是在A都存放vector指针是明显,因为析构时会释放成员变量。(不是最优解)*5)使用shared_ptr,vector不使用new创建,而是使用shared_make,在A构造的时候创建。但是需要注意各个A运行在不同的线程下,同时
操作vector可能产生竞争。 第一种实现为 =============
---A.h---
class A{
public:
A();
A(shared_ptr<vector<classB>> tmplist); public:
shared_ptr<vector<classB>> m_p_B_list; }
---A.cpp---
A::A():m_p_B_list(make_shared<vector<classB>>()){ //默认构造,列表初始化 }
A::A(shared_ptr<vector<classB>> tmplist):m_p_B_list(tmplist){ }
(???)上面的实现虽然使用了共享指针管理动态内存,但是多个A如果都使用默认构造的话,实际上还是创建了多个vector实例,然后各个A
管理自己的vector #7 ===========================================================================================================================
动态分配const对象是合法的,new const string("xxxx");
按照const对象的属性,const必须在定义是初始化,因此这种情况下不能使用默认构造函数。也可以通过delete来释放分配的内存(???)疑问:const类型变量一般存放在 .data段 。那么动态分配的常量是否 也在其中。如果不是,是否存放在堆内,如果是在堆内,那么这块
区域是如何增加读写权限的,毕竟const类型只读不可写。 #8 ===========================================================================================================================
内存耗尽:
int *p = new (nothrow) int; //如果内存耗尽,那么不抛异常bad_alloc,只是返回空指针注:默认情况下,c++中,内存耗尽会抛出异常bad_alloc
#9 ===========================================================================================================================
delete接收一个指针,指针要么指向一个对象,要么是空指针。
delete动作会执行以下两步:1)调用指针指向对象的析构函数;
2)释放指针指向的内存。 如果指针类型和指向的内容不一致,比如 void* p = new classA();
==== 例子 ====
class A{
public:
A(){}
~A(){ cout << "~A()"; }
};class B :public A{
public:
B(){}
~B(){ cout << "~B()"; }
}; int _tmain(int argc, _TCHAR* argv[])
{
//A *p_A = new A();
void *p_B = new B(); //delete p_A;
delete p_B; getchar();
return 0;
} 小结:1)delete只认指针类型,如果指针是void类型,“那么不会调用任何析构函数”。
2)同理,如果有父子关系,那么delete父类指针,“只会调用父类的析构函数”。
(*) 3)为了避免1)和2)中的问题,基类的析构函数必须是 “virtual” 的。
4)不论delete接收的是子类的指针还是基类的指针。类实例都会被完整地释放掉,delete是跟着实例走,只认地址,而一旦涉及
到地址部分,malloc和free便不再理会指针类型,而是从操作系统层面完成内存的释放。 (!!!)注:上面说到了 “基类的析构函数必须是virtual,否则在delete指向子类的基类指针时,会出现子类析构不会
被调用的问题”。如果子类中没有需要回收的内存(通过new和malloc分配的),那么内存是安全的,子类实例
会被正确地释放。但是如果子类中进行了动态内存分配(new,malloc),那么这部分内存就无法再被回收,至此
会导致内存泄露。

#10 ===========================================================================================================================
野指针:野指针是指,指针指向的内存已经被释放掉了,但是指针没有置空。使用delete操作指针并不会触发指针的置空,因此在delete释放
完内存以后,需要手动把指针置空,因为内存区域已经不存在了,此时使用指针去访问就会触发异常 ---> "非法内存区访问"。#11 ===========================================================================================================================
可以在delete后立即置空指针来避免野指针的出现。但是如果某块内存区域被多个指针指向,那么就很容易忽略某个指针的置空操作。
因此:但凡涉及到多个指针指向同一块内存区域的场景,“ 务必使用智能指针 ”来管理内存,此时我们不需要调用delete,只需要管理指针
即可,当所有的指针都置空,则内存被释放。#12 ===========================================================================================================================
上面提到了智能指针的使用方法,通过make_shared来分配内存,但是如果想使用new来分配内存,然后交由智能指针管理,该如何操作???
首先,智能指针的构造函数时explict的,即不接受隐式转换,即必须是指定类型,故先new在通过赋值构造是行不通的。
可以使用复制构造函数来实现:
shared_ptr<classA> p_A = new classA(); //错误
shared_ptr<classA> p_A(new classA()); //正确同理,使用普通指针给智能指针赋值也是行不通的:
shared_ptr<classA> clone(){ //错误
return new classA(); //返回值不接收这种隐式转换
}shared_ptr<classA> clone(){ //正确
return shared_ptr<classA>(new classA()); //返回值也是智能指针
} (!!!)注:上面提到的使用普通指针来初始化智能指针的场景,要求普通指针指向的必须是动态内存(即new分配的),静态内存不行,
比如:
int i = 10;
const int j = 10;
int *p = &i;
const int *pj = &j;
shared_ptr<int> sp(p); //不能把栈给智能指针
shared_ptr<int> spp(pj); //不能把data区给智能指针#(!)13 ===========================================================================================================================
shared_ptr 和 unique_ptr 的构造和析构
classA A;
classA *p_A = &A; //创建一个实例给普通指针unique_ptr<classA> up(new A()); //创建一个实例给unique智能指针
shared_ptr<classA> sp(new A()); //创建一个实例给智能指针shared_ptr<classA> sp1(p_A); //让智能指针指向p_A(普通指针)指向的对象,要求unique_ptr指向的类型和shared_ptr指向的类型能够互换
shared_ptr<classA> sp2(up); //让智能指针指向unique_ptr指向的对象,要求unique_ptr指向的类型和shared_ptr指向的类型能够互换shared_ptr<classA> sp3(p_A,mydelete) //让智能指针指向p_A(普通指针)指向的对象,在所有智能指针都释放以后,不再使用delete释放内存,而是使用mydelete
shared_ptr<classA> sp4(sp1,mydelete) //让智能指针指向sp1(智能指针),并使用mydelete代替delete来释放内存sp.reset() //把sp从当前指向关系中解放出来,如果sp是最后一个指针,那么调用delete释放目标对象
sp.reset(sp1) //把sp重定向到sp1
sp.reset(sp1,mydelete) //把sp中定向到sp1,如果原sp指向的对象已经没有指针再指向,那么使用mydelete来释放原对象 注:如果智能指针指向的不是new出来的动态内存,如果需要释放资源,那么一定要提供mydelete来替代delete,因为这种情况下不会调用delete
#(!)14 ===========================================================================================================================
(!!!)
智能指针的使用:1)使用 普通指针/其他已存在的智能指针/其他已存在的普通指针,对当前创建的智能指针进行初始化。 (创建 指针 指向当前已有内存)
2)使用 make_shared 创建全新的内存区,然后创建一个全新的智能指针指向它。 (创建 内存空间 和 指针)
3)使用 智能指针的 reset 方法来进行 智能指针的 重定向 和 释放 (重定向 和 销毁)#15 ===========================================================================================================================
weak_ptr
weak_ptr 是给 shared_ptr做补充的,把weak_ptr增加到shared_ptr指向的对象上,不会增加计数,单当shared_ptr全部释放完以后,
weak_ptr指向的内容也将不存在。 weak_ptr的使用必须 经过自己的lock()方法,这个方法会peek对象是否存在,存在返回true,否则
false,用完释放weak_ptr亦不会导致计数减少。weak_prt可以理解为shared_ptr的监视器。仅仅用来peek数据。