赞
踩
本文主要介绍C++11及之后的内存管理,包括只能指针,内存分配器等内容。头文件:#include<memory>
智能指针 | 功能 | 独有操作 |
---|---|---|
std::share_ptr | 共享式:允许多个指针指向同一对象 | 1.make_shared<T>(args) 使用args初始化此对象,返回一个shared_ptr ,指向一个动态分配的类型为T的对象; 2.shared_ptr<T>p(q) :p是q的拷贝:会递增q中的计数器; 3.p=q :会递减p所指内存的引用计数,递增q所指内存的引用计数; 4.p.unique() : 若p.use_count() 为1返回true,否则返回false; 5.p.use_count() :返回与p共享对象的智能指针数量,主要用于调试; |
std::unique_ptr | 独占式:独占所指向的对象,它所指向的对象只有一个拥有者 | 1.为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少; 2.可以移动但不能拷贝(仅通过move来转移),一旦转移成功,原来的unique_ptr就失去了对象内存的所有权 |
std::weak_ptr | 弱引用;共享但不引用,不控制对象的声明周期 | shared_ptr和weak_ptr在计数上是原子操作,性能较好; 只有一个接受一个shared_ptr 的构造函数,指向shared_ptr指针指向的对象内存但不拥有该内存;线程安全级别上与stl容器相同 |
允许多个指针指向同一对象, 允许多个对象之间的内存共享,解决多个对象直接共享的内存的管理。
std::weak_ptr
初始化: 构造函数的实参可以是std::weak_ptr
, 但如果是空的,会抛出异常:bad_weak_ptr
std::weak_ptr<int> w;
try {
std::shared_ptr<int> s(w);
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
// 输出:bad_weak_ptr
std::unique_ptr
初始化// 这段代码使用std::unique_ptr初始化std::shared_ptr,完成初始化后q失去了对象管理权,输出:q is null
// 注意:只能move一个unique_ptr初始化shared_ptr
std::unique_ptr<int> q(new int(3));
std::shared_ptr<int> s1(std::move(q)); // 如 std::shared_ptr<int> s1(q);这种是不允许的
if (!q) {
std::cout << "q is null\n";
}
std::shared_ptr
初始化std::shared_ptr<int> s1(new int(2)); // 使用原始指针初始化
std::shared_ptr<int> s2(s1); // 使用std::shared_ptr拷贝初始化
std::cout << s2.use_count() << std::endl;
std::shared_ptr<int> s3(std::move(s2)); // 使用std::shared_ptr移动初始化
if (!s2) {
std::cout << "s2 is null\n";
}
std::cout << s3.use_count() << std::endl;
// 执行结果:2; s2 is null; 2
std::make_shared
初始化std::shared_ptr<int> s4 = std::make_shared<int>(5);
shared_ptr<int>p1 = new int(1024); // 错误,必须使用直接初始化的形式,这样会被视为需要一个隐式转换
shared_ptr<int>p2(new int(1024)); // 正确
std::shared_ptr<int> p(new int[10], std::default_delete<int[]>());
sp.swap(sp1) / swap(sp1, sp2)
:置换sp1和sp2的pointer和deletersp.reset()/sp.reset(ptr)
:放弃拥有权,并重新初始化,拥有empty/ptrsp.get()
:返回原始指针sp.use_count()
:返回共享对象拥有的数量sp.unique()
:返回sp是否是唯一拥有者static_pointer_cast(sp)
:对sp执行static_cast<>
语义dynamic_pointer_cast(sp)
:对sp执行dynamic_cast<>
语义const_pointer_cast(sp)
: 对sp执行const_cast<>
语义shared_ptr
引用计数是线程安全且无锁的(原子操作),但指向的对象不是线程安全的,读写需要加锁独占式拥有,可确保一个对象和其相应资源同一时间只被一个pointer拥有,类定义签名:
// 第一种形式:
template<class T, class Deleter = std::default_delete<T>>
class unique_ptr;
// 第二种形式:
template < class T, class Deleter>
class unique_ptr<T[], Deleter>;
unique_ptr<int> up(new int);
std::unique_ptr<int> q0(new int(2));
std::unique_ptr<int> q1(std::move(q0));
std::make_unique<T>
(C++14开始支持)初始化std::unique_ptr<int> q2 = std::make_unique<int>(5);
up.reset()/up.reset(ptr)
:对智能指针调用deleter,并令为0/重新初始化为ptr,之前的先析构掉了up.get()
:返回原始指针up.release()
:获得unique_ptr拥有的对象并放弃up的拥有权bool()
:检查是否unique pointer是否拥有对象up1.swap(up2)
:置换up1和up2的pointer和deleter// 定义类A
class A {
public:
explicit A(int a) : x_(a) {
std::cout << "A(int a)\n";
}
A(const A& a) : x_(a.x_) {
std::cout << "A(const A& a)\n";
}
A& operator=(const A& a) {
x_ = a.x_;
std::cout << "operator=(const A& a)\n";
return *this;
}
A(A&& a) : x_(a.x_) {
std::cout << "A(A&& a)" << std::endl;
}
A& operator=(A&& a) {
x_ = a.x_;
std::cout << "operator=(A&& a)" << std::endl;
return *this;
}
private:
int x_;
};
定义函数
std::unique_ptr<A> unique_test0() {
std::unique_ptr<A> q = std::make_unique<A>(6);
return q;
}
void unique_test1() {
std::unique_ptr<A> q0 = unique_test0();
}
int main() {
unique_test1();
return 0;
}
A(int a)
(Linux和Win结果一致),说明调用unique_test0函数返回处编译器直接优化为构造,避免了拷贝和移动。*p
:解引用p,获得他指向的对象;p->mem
: 等价于*p
;p.get()
:返回p中保存的指针swap(p, q)/p.swap(q)
:交换p和q中的指针void processWidght(shared_ptr<Widget> pw, int priority) { ... }
// 现在调用processWidget:
processWidget(shared_ptr<Widget>(new Widget), priority());
// 可能会造成资源泄露,因为c++编译器不确定以何种次序完成参数调用,如果先调用new widget,再priority过程中发生异常,new Widget返回的指针就会遗失,所以使用分离语句
shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority())
std::weak_ptr是一种不控制所指向对象生存期间的智能指针,指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
解决循环引用:如果你有两个对象互相持有对方的std::shared_ptr
,那么这两个对象都不会被销毁,因为他们的引用计数永远不会降到0。在这种情况下,你可以使用std::weak_ptr
来打破这个循环。例如,如果你有一个树或图数据结构,你可以使用std::shared_ptr来管理节点之间的父-子关系,然后使用std::weak_ptr来管理节点之间的子-父关系。
缓存:如果你有一个需要大量内存的对象,你可能想要在不需要它时释放它,但是在稍后需要它时又能快速地获取它。在这种情况下,你可以使用std::shared_ptr
来管理这个对象,然后在需要缓存这个对象的地方使用std::weak_ptr
。当std::weak_ptr
被转换为std::shared_ptr
时,如果原始对象已经被销毁,那么这个转换操作将返回一个空的std::shared_ptr
。
观察者模式:在观察者模式中,一个或多个观察者对象“观察”一个主题对象。当主题对象改变时,它会通知所有的观察者对象。在这种情况下,主题对象可以持有观察者对象的std::weak_ptr
,这样即使观察者对象被销毁,主题对象也不会阻止它们被销毁。
两个或多个对象相互引用,形成一个闭环,导致无法正确收回内存。示例如下:
定义两个类
struct B0;
struct A0 {
std::shared_ptr<B0> b;
~A0() {
std::cout << "~A0\n";
}
};
struct B0 {
std::shared_ptr<A0> a;
~B0() {
std::cout << "~B0\n";
}
};
循环引用:
// 导致析构不了:
void cirular_reference1() {
std::shared_ptr<A0> a0 = std::make_shared<A0>();
std::shared_ptr<B0> b0 = std::make_shared<B0>();
a0->b = b0;
b0->a = a0;
}
// 注意,以下代码不是循环引用:
void cirular_reference2() {
A0 a0;
B0 b0;
a0.b = std::make_shared<B0>(b0);
b0.a = std::make_shared<A0>(a0);
}
使用std::weak_ptr代替std::shared_ptr(只替换B0中的变量)解决循环引用无法释放内存的问题:
// 调用cirular_reference1()函数可以正常析构
struct B0
{
std::weak_ptr<A0> a;
~B0() {
std::cout << "~B0\n";
}
};
weak_ptr<T> w
: 空weak_ptr类型weak_ptr<T> w(sp)
:使用shared_ptr初始化weak_ptr,指向与shared_ptr相同的对象,T必需要能转换为sp指向的类型w=p
:p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象wp.swap(wp2)
wp.reset()
:将wp置为空wp.use_count()
:返回与wp共享对象的shared_ptr的数量wp.expired()
:如果use.count()为0返回true,否则为falsewp.lock()
:如果expired()为true,返回一个空shared_ptr,否则返回一个指向w的对象的shared_ptr。wp.owner_before()
如果不确定隐身于weak_ptr背后的对象是否仍旧存活,有以下几个选择:
expired()
:会在weak_ptr不再共享对象时返回true,等同于检查use_count()
是否为0,但速度更快bad_weak_ptr
异常(派生自std::exception
),其what()
函数返回字符串"bad_weak_ptr"use_count()
,询问相应对象的个数;通常只是为调试而使用use_count()
,但这个函数效率低这是一个以派生类为模板类型实参的j基类模板,继承他,this指针就能变身为shared_ptr, 也就是能让一个对象(假设其名为t,被一个shared_ptr对象pt管理)安全的生成其他额外的std::shared_ptr
实例,它们与pt共享对象t的所有权。std::enable_shared_from_this
类给派生类添加了两个方法:
shared_from_this()
:返回一个shared_ptr,它共享对象的所有权。weak_from_this()
:返回一个weak_ptr, 它跟踪对象的所有权class Foo : public std::enable_shared_from_this<Foo> {
};
int *p = new int[10]{0,1,2,3,4,5,6,7,8,9};
allocator模板,将内存分配和对象构造分离开来。它分配的内存是原始的,未构造的。
allocator<T> a; //定义一个名为a的allocator对象
a.allocate(n); //分配一段原始的,未构造的内存,保存n个类型为T的对象
a.construct(p,args);
while( q != p )
alloc.destroy(--q); //q指向最后一个构造的元素
alloc.deallocate(p,n); //释放从p开始的内存,这段内存保存了n个类型为T的对象
拷贝和填充未初始化内存的算法:allocator算法
uninitialized_copy(b, e, b2)
:从迭代器b和e指定的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2的构造必须足够大uninitialized_copy_n(b, n, b2)
:从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中uninitialized_fill(b, e, t)
:从迭代器b和e指定的原始内存中创建对象,对象的值均为t的拷贝uninitialize_fill_n(b, n, t)
:从迭代器b指向的内存地址开始创建n个对象,对象的值都是t的拷贝new/delete
通常用来分配中大型对象(数百个或数千个bytes)void *
,需要通过强制类型转换将void*指针转换成我们需要的类型。operator new
函数(对于数组是operator new[]
)分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。operator delete
(或operator delete[]
)函数释放内存空间。问题 | 对应工具 |
---|---|
缓冲区溢出 | 可以使用vector或string |
空悬指针/野指针 | shared_ptr/weak_ptr配合使用 |
重复释放 | unique_ptr |
内存泄漏 | unique_ptr |
不配对的new[]/delete | vector或者boost中的数组相关的 |
内存碎片 | 内存池 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。