我们在程序运行的过程中,经常出现段错误、内存持续增大等,是C++显式内存管理存在的问题,主要归纳为以下几点:
- 野指针:一些内存单元已经释放,但之前指向它的指针还在使用。
- 重复释放:程序试图释放已经被释放过的内存单元。
- 内存泄漏:没有释放不再使用的内存单元。
- 缓冲区溢出:数组越界。
- 不配对的new[]/delete
针对以上1~3的问题,C++标准中提供了智能指针来解决。
智能指针是基于RAII(Resource Acquisition Is Initialization)机制实现的类(模板),具有指针的行为(重载了operator*与operator->操作符)。当对象创建的时候,进行初始化;离开其作用域后,通过自动调用析构函数释放资源。
C++98中,智能指针通过一个模板类型"auto_ptr"来实现,auto_ptr对象通过初始化指向由new创建的动态内存,当auto_ptr对象生命周期结
束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。但其有一些缺点:
- 赋值和拷贝操作的目标对象会先释放其原来所拥有的对象
auto_ptr<
int
> ap1(
new
int
(
100
));
auto_ptr<
int
> ap2(ap1);
// ap1 == nullptr;
auto_ptr 不能用在stl容器中,因为stl容器要求存储的类型必须为值语义类型。即两个对象在拷贝或赋值之后相等(ap1 == ap2)
- auto_ptr 析构的时候调用的是delete,所以不能用auto_ptr管理数组指针
~auto_ptr() { delete _M_ptr; }
因此,在C++11标准中,改用unique_ptr、shared_ptr及weak_ptr等智能指针来对动态内存进行管理。auto_ptr为了兼容以前的代码被遗留下来,不建议使用。
头文件
<memory>
命名空间为
std
unique_ptr
概念:
- unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现std::move())。* unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现std::move())。
- unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
- unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
基本用法:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Foo {
Foo() {}
~Foo() {}
void
Print() { cout <<
"Foo"
<< endl; }
};
int
main(
void
)
{
Foo* p1 =
new
Foo();
unique_ptr<Foo> up1;
// up1==nullptr
// up1 = p1; // 编译错误,不支持这样赋值
up1.reset(p1);
// 替换管理对象,并释放之前管理的对象
p1 = nullptr;
// unique_ptr<Foo> up2(up1); // 编译错误,不支持这样构造
unique_ptr<Foo> up2(std::move(up1));
// up1所有权转移到up2。up1==nullptr
up1.swap(up2);
// up2与up1管理对象的指针交换。 up2==nullptr
if
(up1) {
// up1 != nullptr
up1->Print();
// unique_ptr重载了->
(*up1).Print();
// unique_ptr重载了*
}
// up2->Print(); // 错误 up2 == nullptr, 必须先判断再调用
p1 = up1.get();
// get() 返回所管理对象的指针, up1继续持有其管理权
p1 = up1.release();
// release() 返回管理对象的指针,并释放管理权,up1==nullptr
delete p1;
unique_ptr<Foo> up3(
new
Foo());
up3.reset();
// 显示释放释放管理对象的内存,也可以这样做:up = nullptr;
vector<unique_ptr<Foo>> v;
unique_ptr<Foo> up4(
new
Foo());
// v.push_back(up4); // 编译错误,不支持这样拷贝
v.push_back(std::move(up4);
// 只能up4放弃对其所有权,通过std::move()将所有权转移到容器中
return
0
;
}
|
应用场景:
- 只要unique_ptr智能指针创建成功,其析构都会被调用,确保动态资源的释放------避免内存泄漏。
- 把unique_ptr作为引用参数,传递给其他例程,不用担心该指针在例程中被copy一份,或不小心释放掉。
shared_ptr
概念:
- shared_ptr 基于“引用计数”模型实现, 多个shared_ptr对象可以拥有同一个动态对象,并维护了一个共享的引用计数。当最后一个指向该对象的shared_ptr被销毁或者reset时,会自动释放其所指的对象,回收动态资源。
- 销毁该对象时,使用默认的delete/delete[]表达式,或者是在构造 shared_ptr 时传入的自定义删除器(deleter),以实现个性化的资源释放动作。
基本用法:
#include <iostream>
#include <memory>
#include <vector>
#include <
assert
.h>
using namespace std;
struct Foo {
int
v;
};
int
main(
void
)
{
shared_ptr<Foo> sp1(
new
Foo{
10
});
cout << sp1.unique() << endl;
// 1 当前shared_ptr唯一拥有Foo管理权时,返回true,否则返回false
cout << sp1.use_count() << endl;
// 1 返回当前对象的引用计数
shared_ptr<Foo> sp2(sp1);
assert
(sp1->v == sp2->v);
// sp1与sp2共同拥有Foo对象
cout << sp2.unique() << endl;
// 0 false
cout << sp2.use_count() << endl;
// 2
sp1.reset();
// 释放对Foo的管理权,同时引用计数减1
assert
(sp1 == nullptr);
// sp1 为空
cout << sp1.unique() << endl;
// 0 不会抛出异常
cout << sp1.use_count() << endl;
// 0 不会抛出异常
cout << sp1.get() << endl;
// 0 不会跑出异常
// cout << sp1->v << endl; // 执行错误 sp1为nullptr时,operator* 和 operator-> 都会导致未定义行为
cout << sp2.unique() << endl;
// 1 true
sp1.swap(sp2);
// sp1与sp2交换管理权,及引用计数
assert
(sp2 == nullptr);
cout << (*sp1).v << endl;
// 10 同sp1->v相同
vector<shared_ptr<Foo>> vec;
vec.push_back(sp1);
cout << sp1.use_count() << endl;
// 2
return
0
;
// vector先析构,里面存储的对象引用计数减1,但不为0,不释放对象
// sp1后析构,引用计数减1,变为0,释放所指对象的内存资源
}
|
主要方法:
T& operator*()
const
;
T* operator->()
const
;
T* get()
const
;
bool unique()
const
;
long
use_count()
const
;
void
swap(shared_ptr<T>& b);
|
应用场景:
1. 在一个map(unordered_map)中,多key索引一个value,使用shared_ptr。
#include <iostream>
#include <memory>
#include <map>
#include <cstdint>
using namespace std;
Class SessionNode {
public
:
SessionNode() {}
virtual ~SessionNode() {}
};
typedef std::shared_ptr<SessionNode> SessionNodeSP;
int
main(
void
)
{
map<uint64_t, SessionNodeSP> map;
uint64_t imsi =
4600000
;
uint32_t tmsi =
0x12345
;
{
SessionNodeSP sp(
new
SessionNode());
map[imsi] = sp;
map[tmsi] = sp;
cout << sp.use_count() << endl;
// 3
}
// sp 销毁,引用计数减1,变为2
map.erase(tmsi);
// use_count()为1
map.erase(imsi);
// use_count()为0,释放被管理对象的内存
return
0
;
}
|
2. 多个map索引同一value
#include <iostream>
#include <memory>
#include <map>
#include <cstdint>
using namespace std;
struct Node {
uint64_t imsi;
uint32_t tmsi;
};
typedef std::shared_ptr<Node> NodeSP;
class
Session {
public
:
Session() {}
~Session() {}
NodeSP GetNode(uint64_t imsi, uint32_t tmsi) {
NodeSP sp(
new
Node{imsi, tmsi});
imsi_map_[imsi] = sp;
tmsi_map_[tmsi] = sp;
return
sp;
}
void
EraseNode(NodeSP& sp) {
if
(sp == nullptr)
return
;
imsi_map_.erase(sp->imsi);
imsi_map_.erase(sp->tmsi);
}
private
:
map<uint64_t, NodeSP> imsi_map_;
map<uint32_t, NodeSP> tmsi_map_;
};
int
main(
void
)
{
Session ses;
uint64_t imsi =
4600000
;
uint32_t tmsi =
0x12345
;
NodeSP sp;
sp = ses.GetNode(imsi, tmsi);
cout << sp.use_count() << endl;
// 3
// ... do something with sp
ses.EraseNode(sp);
cout << sp.use_count() << endl;
// 1
sp.reset();
// 主动释放被管理对象内存
return
0
;
}
|
3. 防止裸指针被删除
#include <iostream>
#include <memory>
class
Cdr {
public
:
Cdr() {}
protected
:
virtual ~Cdr() {}
};
class
SignalCdr :
public
Cdr {
public
:
SignalCdr() {}
virtual ~SignalCdr() {}
};
typedef std::shared_ptr<Cdr> CdrSP;
CdrSP CreateSignalCdr()
{
CdrSP sp(
new
SignalCdr());
return
sp;
};
int
main(
void
)
{
CdrSP sp_cdr = CreateSignalCdr();
SignalCdr* p_signal_cdr = reinterpret_cast<SignalCdr*>(sp_cdr.get());
// ... do something
// delete p_signal_cdr; // 执行错误,~Cdr()为protected
return
0
;
}
|
4. 定制删除器。
#include <iostream>
#include <memory>
#include <errno.h>
#include <stdio.h>
#include <string.h>
using namespace std;
class
FileCloser {
public
:
void
operator()(FILE* p) {
// ... do something
cout <<
"close file"
<< endl;
fclose(p);
}
};
int
main(
void
)
{
try
{
FILE* p_file = fopen(
"./test.cpp"
,
"r"
);
if
(p_file == nullptr)
throw
errno;
shared_ptr<FILE> sp_file(p_file, FileCloser());
// shared_ptr<FILE> sp_file(p_file, &fclose); //如果只需要调用一个单参数的函数,可以直接这么写
// ... do something
}
catch
(
int
& err) {
cout << strerror(err) << endl;
}
return
0
;
}
|
5. 在引起循环引用的时候使用weak_ptr。
#include <iostream>
#include <memory>
using namespace std;
struct Husband;
struct Wife;
typedef std::shared_ptr<Husband> HusbandSP;
typedef std::shared_ptr<Wife> WifeSP;
struct Wife {
~Wife() { cout<<
"wife distroy"
<< endl; }
HusbandSP sp_hb;
};
struct Husband {
~Husband() { cout<<
"husband distroy"
<< endl; }
WifeSP sp_wf;
};
int
main(
void
)
{
{
HusbandSP husband(
new
Husband());
WifeSP wife(
new
Wife());
husband->sp_wf = wife;
wife->sp_hb = husband;
}
// husband 和 wife,相互引用,离开作用域时引用计数都为1,造成内存泄露
return
0
;
}
|
weak_ptr
概念:
- weak_ptr是为了配合shared_ptr而引入的一种智能指针,它只能够通过shared_ptr或者weak_ptr来构造。
- weak_ptr是作为shared_ptr的”观察者“,并不修改shared_ptr所管理对象的引用计数,当shared_ptr销毁时,weak_ptr会被设置为空,所以使用weak_ptr比底层指针的好处在于能够知道所指对象是否有效
- weak_ptr不具有普通指针的行为,因为没有重载operator*和->。所以当weak_ptr观察的对象存在,并且需要修改其内容时,需要提升为shared_ptr来操作。
基本用法:
#include <iostream>
#include <memory>
using namespace std;
struct Cdr{
int
v;
};
typedef std::shared_ptr<Cdr> CdrSP;
typedef std::weak_ptr<Cdr> CdrWP;
void
UpdateCdr(CdrWP wp)
{
if
(wp.expired() ==
false
) {
// 检查被管理对象是否被删除,true 删除,false 没被删除;比use_count()==1要快
CdrSP sp = wp.lock();
// 提升为强引用
// CdrSP sp(wp); // 另一种wp提升为强引用方法
if
(sp != nullptr) {
// 若提升失败,shared_ptr 为 nullptr,此例子不会失败
sp->v *=
2
;
cout << sp.use_count() << endl;
// 此时引用计数为2
}
}
// sp删除,引用计数减1
wp.reset();
// 显示释放所有权,或者等离开作用域会自动释放
}
int
main(
void
)
{
CdrSP sp(
new
Cdr{
10
});
UpdateCdr(sp);
// 对sp进行操作
cout << sp->v << endl;
// 20
return
0
;
}
|
主要方法:
long
use_count()
const
;
bool expired()
const
;
std::shared_ptr<T> lock()
const
;
|
应用场景:
- 如基本用法,通过weak_ptr观察shared_ptr管理的对象,如果有效,提升为shared_ptr进行操作。
- map中存储弱引用
#include <iostream>
#include <memory>
#include <map>
using namespace std;
struct Cdr {
int
v;
};
typedef std::shared_ptr<Cdr> CdrSP;
typedef std::weak_ptr<Cdr> CdrWP;
class
Cache {
public
:
Cache() {}
~Cache() {}
void
UpdateIndex(CdrSP& sp) {
if
(sp != nullptr && sp->v >
0
)
cdr_map_[sp->v] = sp;
}
private
:
map<
int
, CdrWP> cdr_map_;
};
int
main(
void
)
{
Cache cache;
CdrSP sp_cdr(
new
Cdr{
1
});
cache.UpdateIndex(sp_cdr);
cout << sp_cdr.use_count() << endl;
// 1
return
0
;
// sp_cdr销毁,引用计数为1,释放管理对象内存
// cache销毁,因为map中存储的为weak_ptr,weap_ptr为空,所以不会产生二次释放
}
bad_weak_ptr异常捕获
当shared_ptr通过weak_ptr参数构造,而weak_ptr指向一个已经被删除的对象时,会抛出std::bad_weak_ptr异常。
#include <iostream>
#include <memory>
using namespace std;
int
main()
{
shared_ptr<
int
> sp1(
new
int
(
1
));
weak_ptr<
int
> wp(sp1);
p1.reset();
try
{
shared_ptr<
int
> sp2(wp);
}
catch
(
const
std::bad_weak_ptr& e) {
cout << e.what() << endl;
// "std::bad_weak_ptr"
}
}
|
从this创建shared_ptr
有时候,需要从this获得 shared_ptr ,即是说,你希望你的类被shared_ptr所管理,你需要把"自身"转换为shared_ptr的方法。
#include <iostream>
#include <memory>
using namespace std;
class
Foo;
typedef std::shared_ptr<Foo> FooSP;
void
DoSomething(
const
FooSP& sp) {
cout << sp.use_count() << endl;
// 2
}
class
Foo :
public
std::enable_shared_from_this<Foo> {
public
:
Foo() {}
~Foo() {}
void
Do() {
DoSomething(shared_from_this());
}
};
int
main(
void
)
{
FooSP sp(
new
Foo());
cout << sp.use_count() << endl;
// 1
sp->Do();
return
0
;
}
|
总结:
- unique_ptr/shared_ptr中,get()把底层指针暴露出来,为的是兼容老程序,一般不提倡使用,因为很难确保别的例程会对这个指针做什么,比如说delete/delete[]。
- 作为shared_ptr和weak_pt作为参数传递时,使用引用传递以减小开销。
- weak_ptr 没有重载operator==,所以不能比较。shared_ptr可以比较,比较的是里面底层指针。
- auto_ptr、unique_ptr、shared_ptr,不能这样用
{
int
* p =
new
int
(
100
);
auto_ptr<
int
> ap1(p);
auto_ptr<
int
> ap2(p);
}
当离开作用域,ap1和ap2都试图删除p,会造成double free。
- 使用智能指针虽然不需要手动处理引用计数和调用delete来释放资源。但要清楚什么时候资源会被释放。
例如:用容器来存储shared_ptr,并且从容器中删除的时候释放资源,那么其他例程在使用shared_ptr时只是更新其资源;
如果从容器中删除shared_ptr时不释放资源,那么应该被另外一个shared_ptr所共享;这个时候容器中存储weak_ptr更合适。
引用:
英文网址:
http://en.cppreference.com/w/cpp/memory
中文网址(google 机器翻译):
http://zh.cppreference.com/w/cpp/memory