1. 动态内存必要性
程序不知道自己需要多少对象;
程序不知道对象的准确类型;
程序需要在多个对象之间共享数据;
2. 动态内存在哪里
程序有静态内存、栈内存。静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建或销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式的销毁它们。(c++ primer P400)
3. 自由存储区和堆
自由存储是c++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区
堆是操作系统维护的一块内存
虽然c++编译器默认使用堆来实现自由存储。但两者不能等价
4. 动态内存与智能指针
我们知道c++需要注意的地方之一就是对内存的管理,动态内存的使用经常会出现内存泄漏,或者产生引用非法内存的指针
c++11标准库提供了三种智能指针类型来管理动态对象:
智能指针:自动负责释放所指向的对象,实际上它利用了栈的机制,每一个智能指针都是一个模板类,调用智能指针实际上是创建了一个智能指针的对象,对象生命周期到达尽头的时候,会自动调用智能指针的析构函数,在析构函数里,释放掉它管理的内存,从而避免手动delete。
- shared_ptr 允许多个指针指向同一个对象
- unique_ptr 独占所指向的对象
- weak_ptr shared_ptr的弱引用
- auto_ptr c++98提出 c++11 摒弃 对于特定对象,和unique_ptr一样只能被一个智能指针所拥有,这样,只有拥有该对象的智能指针的析构函数才会删除该对象
定义在memory头文件中,他们的作用在于会自动释放所指向的对象
5. 智能指针的详细描述
5.1 auto_ptr描述
auto_ptr主要是用来解决资源自动释放的问题,比如如下代码:
-
void
Function()
-
{
-
Obj*p =
new Obj(
20);
-
...
-
if (error occor)
-
throw ... 或者 retrun;
-
delete p;
-
}
在函数遇到错误之后,一般会抛异常,或者返回,但是这时很可能遗漏之前申请的资源,及时是很有经验的程序员也有可能出现这种错误.
而使用auto_ptr会在自己的析够函数中进行资源释放。也就是所说的RAII
使用auto_ptr代码如下
-
void
Function()
-
{
-
auto_ptr<Obj> ptr(
new Obj(
20) );
-
...
-
if (error occur)
-
throw
exception...
-
}
这样无论函数是否发生异常,在何处返回,资源都会自动释放。
需要提一下的是这是一个被c++11标准废弃的一个智能指针,为什么会被废弃,先看一下下面的代码:
-
auto_ptr<Obj> ptr1(
new Obj() );
-
ptr1->FuncA();
-
auto_ptr<Obj> ptr2 = ptr1;
-
ptr2->FuncA();
-
ptr1->FuncA();
// 这句话会异常
为什么在把ptr1复制给ptr2之后ptr1再使用就异常了呢?
这也正是他被抛弃的主要原因。
因为auto_ptr复制构造函数中把真是引用的内存指针进行的转移,也就是从ptr1转移给了ptr2,此时,ptr2引用了Obj内存地址,而ptr1引用的内存地址为空,此时再使用ptr1就异常了。
5.2 shared_ptr描述(in memory):
shared_ptr是一个标准的共享所有权的智能指针,就是允许多个指针指向同一对象,shared_ptr对象中不仅有一个指针指向某某(比如 int型,以下也拿int类型举例)对象,还拥有一个引用计数器,代表一共有多少指针指向了那个对象。
为什么shared_ptr允许多个指针指向同一对象?
因为 动态对象的所有权不确定。对象可以在多个作用域中共享,又不能像栈对象一样自由地值拷贝。只要有一个对象\作用域还持有这个动态对象,他就不能销毁,当他没有用时,自动销毁。
shared_ptr自动销毁所管理的对象
每当创建一个shared_ptr的对象指向int型数据,则引用计数器值+1,每当销毁一个shared_ptr对象,则-1.当引用计数器数据为0时,shared_ptr的析构函数会销毁int型对象,并释放它占用的内存。
shared_ptr和new的配合使用
接受指针作为参数的智能指针的构造函数是explicit类型,意味着必须使用直接初始化,不能做隐式类型转换
-
shared_ptr<
int> p1;
-
//被初始化成为一个空指针
-
-
shared_ptr<
int> p2 (
new
int(
4));
-
//指向一个值是4的int类型数据
-
-
shared_ptr<
int> p3 =
new
int(
4);
-
//错误,必须直接初始化
-
shared_ptr<
int> Fun(
int p)
-
{
-
return
new
int(p);
//error
-
return
shared_ptr<
int>(
new
int(p));
//right
-
}
不能混合使用普通指针和智能指针,因为智能指针不是单纯的赤裸裸的指针
-
void process(shared_ptr<int> ptr){
-
//受到参数值传递的影响,ptr被构造并且诞生,执行完函数块后被释放
-
}
-
-
int *x(
new
int (
43));
-
//x是一个普通的指针
-
-
process(x);
-
//错误,int * 不能转换成shared_ptr<int>类型
-
-
process(shared_ptr<
int> x);
-
//临时创造了x,引用数+1,执行完process之后,引用数-1
-
-
int j = *x;
-
//x是一个空悬指针,是未定义的
不能使用get()函数对智能指针赋值或初始化
原因:get()函数得到的是共享对象的地址,是内置指针,指向智能指针管理的对象,而智能指针不仅仅包含地址,两个东西不是一个类型的,也不能彼此包含,因此不能这样做。
同样,把get()返回值 绑定到智能指针上也是错误的
如下:
-
shared_ptr<
int> p (
new
int(
22));
-
int *q = p.get();
-
//语义没问题
-
-
{
-
shared_ptr<
int> (q);
-
//意味着q被绑定,!!!!引用计数器还是1!!!!
-
//如果这个被执行,程序块结束以后q和q所指的内容被销毁,则代表着以后执行(*p)的解引用操作,就成了未定义的了。
-
}
-
-
int r = *p;
-
//已经不对了,因为p指向的内存已经在刚才那个代码块里被q释放了
shared_ptr的一些操作
-
shared_ptr<
T> p;
-
//空智能指针,可指向类型是T的对象
-
-
if(p)
-
//如果p指向一个对象,则是true
-
-
(*p)
-
//解引用获取指针所指向的对象
-
-
p -> number == (*p).number;
-
-
p.
get();
-
//返回p中保存的指针
-
-
swap(p,q);
-
//交换p q指针
-
-
p.
swap(q);
-
//交换p,q指针
-
-
make_shared<
T>(args)
-
//返回一个shared_ptr的对象,指向一个动态类型分配为T的对象,用args初始化这个T对象
-
-
shared_ptr<
T> p(q)
-
//p 是q的拷贝,q的计数器++,这个的使用前提是q的类型能够转化成是T*
-
-
shared_pts<
T> p(q,d)
-
//p是q的拷贝,p将用可调用对象d代替delete
-
//上面这个我其实没懂,也没有查出来这个的意思
-
-
p =q;
-
//p的引用计数-1,q的+1,p为零释放所管理的内存
-
-
p.unique();
-
//判断引用计数是否是1,是,返回true
-
-
p.use_count();
-
//返回和p共享对象的智能指针数量
-
-
p.reset();
-
p.reset(q);
-
p.reset(q,d);
-
//reset()没懂,这个以后再来补充吧
shared_ptr 强引用和弱引用
强引用和弱引用就是shared_ptr用来维护引用计数的信息
- 强引用
用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).
- 弱引用
用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).
- 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象
- 当指向一个对象的最后一个shared_ptr被销毁,shared_ptr类会自通过调用对应的析构函数销毁此对象
- shared_ptr会自动释放相关联的内存
-
//该函数返回一个T类型的动态分配的对象,对象是通过一个类型为Q的参数来进行初始化的
-
shared_ptr<T> Fun(Q arg)
-
{
-
//对arg进行处理
-
//shared_ptr负责释放内存
-
return make_shared<T>(arg);
-
//由于返回的是shared_ptr,我们可以保证他分配的对象会在恰当的时候释放
-
}
-
-
void use_Fun(Q arg)
-
{
-
shared_ptr<T> p = Fun(arg);
-
//使用p
-
}
-
//函数结束,p离开了作用域,他指向的内存被自动释放
-
-
shared_ptr<T> use_Fun(Q arg)
-
{
-
shared_ptr<T> p = Fun(arg);
-
//使用p
-
return p;
//返回p时,引用计数递增
-
}
-
//p离开了作用域,但他不会释放指向的内存
shared_ptr 测试例子
-
#define _CRT_SECURE_NO_WARNINGS
-
-
#include <iostream>
-
#include <string>
-
#include <memory>
-
#include <vector>
-
#include <map>
-
-
-
void mytest()
-
{
-
std::
shared_ptr<
int> sp1(
new
int(
22));
-
std::
shared_ptr<
int> sp2 = sp1;
-
std::
cout <<
"cout: " << sp2.use_count() <<
std::
endl;
// 打印引用计数
-
-
std::
cout << *sp1 <<
std::
endl;
-
std::
cout << *sp2 <<
std::
endl;
-
-
sp1.reset();
// 显示让引用计数减一
-
std::
cout <<
"count: " << sp2.use_count() <<
std::
endl;
// 打印引用计数
-
-
std::
cout << *sp2 <<
std::
endl;
// 22
-
-
return;
-
}
-
-
int main()
-
{
-
mytest();
-
-
system(
"pause");
-
return
0;
-
}
5.3 unique_ptr描述(in memory):
unique_ptr的直观认知应该就是“独占”、“拥有”,与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象即不能拷贝和赋值。因此,当unique_ptr被销毁,它所指的对象也会被销毁。
unique_ptr的“独占”是指:不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。例如:
-
std::
unique_ptr<
int> p (
new
int);
-
std::
unique_ptr<
int> q = p;
//error
但是unique_ptr允许通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,注意,这时它本身就不再拥有原来指针的所有权了。后面都会提到
unique_ptr的初始化必须采用直接初始化
-
unique_ptr<
string> p(
new
string(
"China"));
-
//没问题
-
-
unique_ptr<
string> p (q);
-
//错误,不支持拷贝
-
-
unique_ptr<
string> q;
-
-
q = p;
-
//错误,不支持赋值
-
-
unique_ptr的一些操作:
-
unique_ptr<T> p;
-
//空智能指针,可指向类型是T的对象
-
-
if(p)
-
//如果p指向一个对象,则是true
-
-
(*p)
-
//解引用获取指针所指向的对象
-
-
p -> number == (*p).number;
-
-
p.get();
-
//返回p中保存的指针
-
-
swap(p,q);
-
//交换p q指针
-
-
p.swap(q);
-
//交换p,q指针
-
-
unique_ptr<T,D>p;
-
//p使用D类型的可调用对象释放它的指针
-
-
p =
nullptr;
-
//释放对象,将p置空
-
-
p.release();
-
//p放弃对指针的控制,返回指针,p置数空
-
-
p.reset();
-
//释放p指向的对象
-
-
p.reset(q);
-
//让u指向内置指针q
unique_ptr作为参数传递和返回值,是可以拷贝或者赋值的
unique_ptr的不能拷贝有一个例外:
-
unique_ptr<
int> Fun(
int p)
-
{
-
return
unique_ptr<
int>(
new
int(p));
-
}
-
//返回一个局部对象的拷贝
-
unique_ptr<
int> Fun(
int p)
-
{
-
unique_ptr<
int> ret(
new
int(p));
-
//...
-
return ret;
-
}
编译器知道要返回的对象将要被销毁,执行了一种特殊而“拷贝”(移动操作)
如何安全的重用unique_ptr指针
要安全的重用unique_ptr指针,可给它赋新值。C++为其提供了std::move()方法。
-
unique_ptr<
string> pu1(
new
string(
"nihao"));
-
unique_ptr<
string> pu2;
-
pu2 =
std::move(pu1);
//move
-
cout<<*pu1<<
endl;
//赋新值
比较而言auto_ptr由于策略没有unique_ptr严格,无需使用move方法
-
auto_ptr<
string> pu1, pu2;
-
pu1 = demo2(
"Uniquely special");
-
pu2 = pu1;
-
pu1 = demo2(
" and more");
-
cout<<*pu2<<*pu1<<
endl;
由于unique_ptr使用了C++11新增的移动构造函数和右值引用,所以可以区分安全和不安全的用法。
unique_ptr相较auto_ptr提供了可用于数组的变体
auto_ptr和shared_ptr可以和new一起使用,但不可以和new[]一起使用,但是unique_ptr可以和new[]一起使用
-
unique_ptr<
double[]> pda(
new
double(
5));
-
pda.release();
-
//自动用delete[]销毁其指针释放内存
unique_ptr 测试例子
-
#define _CRT_SECURE_NO_WARNINGS
-
-
#include <iostream>
-
#include <string>
-
#include <memory>
-
#include <vector>
-
#include <map>
-
-
-
void mytest()
-
{
-
std::
unique_ptr<
int> up1(
new
int(
11));
// 无法复制的unique_ptr
-
//unique_ptr<int> up2 = up1; // err, 不能通过编译
-
std::
cout << *up1 <<
std::
endl;
// 11
-
-
std::
unique_ptr<
int> up3 =
std::move(up1);
// 现在p3是数据的唯一的unique_ptr
-
-
std::
cout << *up3 <<
std::
endl;
// 11
-
//std::cout << *up1 << std::endl; // err, 运行时错误
-
up3.reset();
// 显式释放内存
-
up1.reset();
// 不会导致运行时错误
-
//std::cout << *up3 << std::endl; // err, 运行时错误
-
-
std::
unique_ptr<
int> up4(
new
int(
22));
// 无法复制的unique_ptr
-
up4.reset(
new
int(
44));
//"绑定"动态对象
-
std::
cout << *up4 <<
std::
endl;
// 44
-
-
up4 =
nullptr;
//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价
-
-
std::
unique_ptr<
int> up5(
new
int(
55));
-
int *p = up5.release();
//只是释放控制权,不会释放内存
-
std::
cout << *p <<
std::
endl;
-
//cout << *up5 << endl; // err, 运行时错误
-
delete p;
//释放堆区资源
-
-
return;
-
}
-
-
int main()
-
{
-
mytest();
-
-
system(
"pause");
-
return
0;
-
}
5.4 weak_ptr描述(in memory)
weak_ptr是一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象,但是不影响shared_ptr的引用计数。它像shared_ptr的助手,一旦最后一个shared_ptr被销毁,对象就被释放,weak_ptr不影响这个过程。
- weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但我们可以通过lock来获得一个shared_ptr对象来对资源进行使用,如果引用的资源已经释放,lock()函数将返回一个存储空指针的shared_ptr。 expired函数用来判断资源是否失效。
- weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。
注意:weak_ptr并不拥有资源的所有权,所以不能直接使用资源。
可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权。
weak_ptr的一些操作:
-
weak_ptr<T> w(sp);
-
//定义一个和shared_ptr sp指向相同对象的weak_ptr w,T必须能转化成sp指向的类型
-
-
w = p;
-
//p是shared_ptr或者weak_ptr,w和p共享对象
-
-
w.reset();
-
//w置为空
-
-
w.use_count();
-
//计算与w共享对象的shared_ptr个数
-
-
w.expired();
-
//w.use_count()为0,返回true
-
-
w.
lock();
-
//w.expired()为true,返回空shared_ptr,否则返回w指向对象的shared_ptr
weak_ptr 测试例子
-
#define _CRT_SECURE_NO_WARNINGS
-
-
#include <iostream>
-
#include <string>
-
#include <memory>
-
#include <vector>
-
#include <map>
-
-
void check(std::weak_ptr<int> &wp)
-
{
-
std::
shared_ptr<
int> sp = wp.lock();
// 转换为shared_ptr<int>
-
if (sp !=
nullptr)
-
{
-
std::
cout <<
"still: " << *sp <<
std::
endl;
-
}
-
else
-
{
-
std::
cout <<
"still: " <<
"pointer is invalid" <<
std::
endl;
-
}
-
}
-
-
-
void mytest()
-
{
-
std::
shared_ptr<
int> sp1(
new
int(
22));
-
std::
shared_ptr<
int> sp2 = sp1;
-
std::weak_ptr<
int> wp = sp1;
// 指向shared_ptr<int>所指对象
-
-
std::
cout <<
"count: " << wp.use_count() <<
std::
endl;
// count: 2
-
std::
cout << *sp1 <<
std::
endl;
// 22
-
std::
cout << *sp2 <<
std::
endl;
// 22
-
check(wp);
// still: 22
-
-
sp1.reset();
-
std::
cout <<
"count: " << wp.use_count() <<
std::
endl;
// count: 1
-
std::
cout << *sp2 <<
std::
endl;
// 22
-
check(wp);
// still: 22
-
-
sp2.reset();
-
std::
cout <<
"count: " << wp.use_count() <<
std::
endl;
// count: 0
-
check(wp);
// still: pointer is invalid
-
-
return;
-
}
-
-
int main()
-
{
-
mytest();
-
-
system(
"pause");
-
return
0;
-
}
6. 如何选择智能指针:
-
#include <iostream>
-
#include <memory>
-
#include <vector>
-
#include <algorithm>
-
#include <stdlib.h>
-
using
namespace
std;
-
-
unique_ptr<
int> make_int(
int n)
-
{
-
return
unique_ptr<
int> (
new
int(n));
-
}
-
-
void show(unique_ptr<int> & pi) //pass by reference
-
{
-
cout<< *pi <<
' ';
-
}
-
-
int main()
-
{
-
vector<
unique_ptr<
int> > vp(
5);
-
for(
int i =
0; i < vp.size(); ++i)
-
{
-
vp[i] = make_int(rand() %
1000);
//copy temporary unique_ptr
-
}
-
vp.push_back(make_int(rand() %
1000));
//ok because arg is temporary
-
for_each(vp.begin(), vp.end(), show);
-
unique_ptr<
int> pup(make_int(rand() %
1000));
-
// shared_ptr<int> spp(pup);//not allowed. pup is lvalue
-
shared_ptr<
int> spr(make_int(rand() %
1000));
-
-
return
0;
-
}
总结:
- 当多个对象指向同一个对象的指针时,应选择shared_ptr
- 用new申请的内存,返回指向这块内存的指针时,选择unique_ptr就不错
- 在满足unique_ptr要求的条件时,前提是没有不明确的赋值,也可以使用auto_ptr
- 如上述代码所示,unique_ptr为右值(不准确的说类似无法寻址)时,可以赋给shared_ptr
- 尽量使用unique_ptr而不要使用auto_ptr
- 一般来说shared_ptr能够满足我们大部分的需求
- weak_ptr可以避免递归的依赖关系
参考链接:
https://blog.csdn.net/derkampf/article/details/72654883
https://blog.csdn.net/weixin_36888577/article/details/80188414
https://blog.csdn.net/zhuziyu1157817544/article/details/64927834
https://blog.csdn.net/zsc_976529378/article/details/52250597