动态内存与智能指针
动态内存的使用十分容易出现问题(内存泄漏/非法内存),而智能指针能更安全、容易的使用动态内存,因为他负责自动释放所指向的对象,并且在出现异常时,也会自动释放。
两种智能指针,区别是管理底层指针的方式:
shared_ptr :允许多指针指向同一个对象
unique_ptr:独占所指向的对象
weak_ptr:弱引用,指向shared_ptr所管理的对象。
auto_ptr:和weak相比,主要差异是支持拷贝构造与赋值操作
shared_ptr p1; ///< 指向string
shared_ptr <list> p2; ///< 指向int的list
默认初始化的智能指针中保存一个空指针,使用方法与指针类似
shared_ptr和unique_ptr共有操作
shared_ptr sp unique_ptr up 默认初始化空智能指针
使用智能指针:p *p p->mem 与指针类似
p.get() 返回p中保存的指针。(若智能指针释放,则返回指针所指向对象也释放了)
swap(p,q) p.swap(q)交换p和q的指针
shared_ptr独有操作
make_shared(args) 返回shared_ptr,指向一个动态分配,以args初始化类型为T的对象
shared_ptrp(q)p是q的拷贝,此操作会递增q中计数器
shared_ptrp(q,d)p是q的拷贝,此操作会递增q中计数器,d用于代替delete函数
p=q递减p引用计数,递增q引用计数,引用为0时释放内存
p.use_count() 返回和p共享对象的智能指针数量
p.unique 若count 为1 返回true
关于new分配内存的操作
shared_ptr p(q) q指向new分配的T*类型数据,p管理该对象
shared_ptr p(u) 从unique_ptr对象u接管所有权,u置为空
shared_ptr p(q,d) p接管new出来的q,并且使用d函数代替delete函数
p.reset()、p.reset(q)、p.reset(q,d) 若有d,则使用d代替delete,若有q,则令p指向q,若都无,则代表释放此对象。
unique_ptr操作
/// release 放弃控制权,返回指针并置为空(不会释放之前指向的对象)
/// reset(q) 释放指向的对象,并指向新对象q,若为空,则只是释放操作
int main(int argc, char **argv)
{unique_ptr<int> uni1(new int(1));unique_ptr<int> uni2(uni1.release()); /// uni1放弃对指针的控制权,返回指针,并将uni1置为空unique_ptr<int> uni3(new int(3));uni1.reset(new int(4)); /// 释放uni1指向的对象,并让uni1指向新的对象uni1.reset(nullptr); /// 释放uni1指向的对象,并让uni1指向null cout << uni1.get() <<endl;if (uni1.get() != NULL){cout << *uni1 <<endl;}cout << uni2.get() <<endl;if (uni2.get() != NULL){cout << *uni2 <<endl;}
}
智能指针使用
智能指针管理对象原理:每个shared_ptr都有一个关联的计数器,称为引用计数,当引用计数为0时,会自动释放所管理的对象。
make_shared初始化
使用动态内存最安全的方法就是使用make_shared
shared_ptr p = make_shared(42); /// 指向int的shared_ptr
shared_ptr p = make_shared(42); ///指向string “9999999999”
auto p = make_shared<vector>
p = q; p内存自动释放 q引用递增
/// p的作用域结束时,调用析构函数,指向的内存自动释放,而return p时,引用计数发生了递增。
使用类实现数据共享
智能指针加容器
initializer_list v 代表是初始化列表传递
shared_ptr 和 new 结合使用
shared_ptr p = new int(40); /// 错误。不能将普通指针隐式初始化为智能指针
shared_ptr p(new int (42)); /// 正确,直接初始化
当shared_ptr绑定到一个普通指针时,内存管理的责任将交给shared_ptr,不应该再使用内置指针访问被管理的内存,因为此时内置指针访问时,智能指针是不知道的。所以建议在定义智能指针时直接初始化动态内存。
智能指针的异常兜底
void func()
{int *p = new int(42);/// 若此时抛出异常,且未进行捕获操作,则发生内存泄漏delete p;
}void func()
{shared_ptr <int> sp(new int (42));/// 抛出异常且未捕获/// 函数结束时自动释放内存
}
unique_ptr
与shared_ptr的区别是,它是独占式,并且没有make_shared操作,需要绑定到new 返回的指针上。
unique_ptr p1; /// 正确,指向double
unique_ptr p2(new int(42)) ///正确,指向值为42的int
因为unique_ptr是独占式,所以不支持普通的拷贝或赋值,但有例外,可以拷贝或赋值一个将要被销毁的unique_ptr,最常见的就是函数中返回一个unique_ptr。
重写unique_ptr的删除器代替delete
decltype关键字
auto和decltype都用于自动类型推导,但用法有差别。
auto valname = value;
decltype(exp) varname = value;
decltype(exp) varname;
///auto根据右边的初始值value,推导变量类型,而decltype根据exp表达式,推导出变量类型
///,auto必须要初始化
如果 exp 是一个表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致。
如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。所以不能返回void类型
如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。
如果exp是函数类型,那么返回也是函数类型。加上*后变为函数指针
weak_ptr
弱引用智能指针:不控制所指向对象的生存周期,指向由shared_ptr管理的对象,使用前必须调用检查对象是否还存在。
weak_ptr绑定到shared_ptr之后,不会改变它的引用计数,实现“弱”共享对象。
auto p = make_shared(42);
if (shared_ptr np = wp.lock()) { ///检查对象是否还存在,存在才能访问,保证安全
weak_ptr wp§; ///弱共享p,不改变p的引用计数。
}
注意事项
1、get 用来将指针的访问权限传递给代码,仅限于某些不能传递智能指针的场合。因为get到指针地址后,如果用该普通指针初始化智能指针,并不会增加引用计数,之后可能发生double free。(不能用于初始化或reset另一个智能指针,不能delete)
2、不使用相同的内置指针初始化(或reset)多个智能指针。(多个智能指针管理同一片内存,并且互不感知)
3、如果智能指针管理的资源,不是new出来的内存,需要传递一个删除器代替delete操作
4、智能指针的内存泄漏问题:当两个shared_ptr互相指向,则会造成循环引用,引用计数始终不为0,导致内存泄漏。解决办法:将其中一个share_ptr改为弱引用指针weak_ptr,弱引用指针不修改引用计数的值,从而不会对对象的内存进行管理。并且它可以检测到所管理对象是否已释放,避免非法访问
5、引用计数并不是原子操作,第一步复制内容的ptr指针,第二步引用计数+1,所以多线程需要加锁
动态数组
.。。。。。。。。。。。。。待完善