动态内存与智能指针
Posted the_scent_of_th_soul
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态内存与智能指针相关的知识,希望对你有一定的参考价值。
我们先来看一些对象的生存期。全局对象在程序启动时分配,在程序结束时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。局部自动对象,在进入其定义所在的程序块儿时被创建,离开块时销毁。即,它们都是由编译器自动创建与销毁。
而动态分配的对象的生存期与它们在哪里创建的无关,只有当显式地被释放时,这些对象才销毁。
在C++中,动态内存的管理是通过一对运算符来完成的:
new:在动态内存为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化。
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出问题,这里列举如下几点:
缓冲区溢出
空悬指针/野指针
重复释放: 释放已经被释放过了的内存
内存泄漏: 忘记释放内存
不配对的new[]/delete
先列这几条,等先了解智能指针的概念以后在来看怎么用智能指针解决这些问题。
为了更安全(同时也更容易)地使用动态内存,标准库提供了智能指针类型来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。
我们首先来看一下shared_ptr类,该类型在memory头文件中。
shared_ptr类
智能指针也是模板。因此在创建一个智能指针时,必须提供它可以指向的类型。
....
shared_ptr<string> p1; //shared_ptr,可以指向string
shared_ptr<list<int>> p2; //shared_ptr,可以指向int的list
....
默认初始化的智能指针保存着一个空指针。类似与普通指针,解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果是检测它是否为空。
//如果p1指向一个空string,解引用p1,将一个新值给string
if(p1 && p1->empty())
*p1 = "hi";
最安全的分配和使用动态内存的方法是调用标准库函数make_shared。定义在头文件memory中。该函数在动态内存中分配一个对象并初始化
,返回指向此对象的shared_ptr。make_shared也是一个模板,使用方法如下:
//指向一个值为42的int对象的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
//p4指向一个"9999999999"的string对象
shared_ptr<string> p4 = make_shared<string>(10,'9');
//p5指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();
make_shared用其参数来构造给定类型的对象。例如,调用make_shared时传递的参数必须与string的某个构造函数相匹配。通常用auto定义一个对象来保存make_shared的结果:
//p6指向一个动态分配的vector<string>
auto p6 = make_shared<vector<string>>();
shared_ptr的拷贝和赋值
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象。
auto p = make_shared<int>(42); //p指向的对象只有p一个引用者
auto q(p); //p和q指向相同的对象,此对象有两个引用者。
可以认为每个shared_ptr都有一个关联的计数器(引用计数)。无论何时,拷贝一个shared_ptr,计数器都会递增。例如:
用一个shared_ptr初始化另外一个shared_ptr
将一个shared_ptr作为一个参数传递给一个函数
当一个shared_ptr作为一个函数的返回值
相对的,给一个shared_ptr赋予一个新值或者shared_ptr被销毁时,计数器就会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
auto r = make_shared<int>(42);
r = q; //q指向的对象的引用计数递增。
//r原来指向的对象的引用计数递减。
//r原来指向的对象没有了引用者,会自动释放。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过析构函数来完成销毁动作的。shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数边为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。
但是,如果你忘记了销毁程序不需要的shared_ptr,程序仍会正确执行,但会浪费内存。一种可能的情况是,将shared_ptr放在一个容器里,当不需要某些元素时,应该确保用erase删除那些不需要的shared_ptr元素。
程序使用动态生存期的资源的类 出于以下三种原因之一:
程序不知道自己需要使用多少对象(空间)例如,容器类
程序不知道所需对象的准确类型
程序需要在多个对象间共享数据
下面是一个需要在多个对象间共享数据的例子:
我们定义一个strblob类,保存一组元素。与容器不同,我们希望strblob对象的不同拷贝之间共享相同的元素。我们可以用一个vector来保存元素,但是,不能在一个strblob对象内直接保存vector,因为一个对象的数据成员在对象销毁时也会被销毁。为了实现strblob对象共享相同的底层数据,我们可以将vector保存在动态内存中,然后为每个strblob设置一个shared_ptr来管理动态内存分配的vector。这个shared_ptr的成员将记录有多少个shared_ptr共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。
#ifndef _STRBLOB_H
#define _STRBLOB_H
#include<string>
#include<memory>
#include<list>
#include<vector>
using namespace std;
class strblob
public:
strblob();
strblob(std::initializer_list<std::string> s);
void print()
for(auto v : *data_)
std::cout << v << std::endl;
private:
std::shared_ptr<std::vector<std::string>> data_;
;
strblob::strblob():data_( make_shared<vector<string>>() )
strblob::strblob(initializer_list<string> s):data_(make_shared<vector<string>>(s))
#endif
下面是一个测试用例
#include"strblob.h"
int main(int argc,char *argv[])
std::initializer_list<std::string> s = "hello";
strblob A; //使用不带参数的构造函数
strblob B(s); //使用带参数的构造函数
A.print();
B.print();
std::cout << std::endl;
//B对象的shred_ptr指向的对象的引用计数递增,A的递减并且释放A的shared_ptr指向的对象
A = B; //使用默认赋值
A.print();
B.print();
return 0;
其他shared_ptr操作:
我们可以用reset来将一个新的指针赋予一个shared_ptr
p.reset(); //若p是唯一指向对象的shared_ptr,reset会释放此对象。
p.reset(q); //若传递了可选参数内置指针q,会令p指向q,否则会将p置为空。
p.reset(q,d); //如果还传递了参数d,将会调用d而不是delete来释放q
weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。
当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p; p的引用计数未改变。
由于waek_ptr指向的对象可能不存在,因此我们不能直接使用weak_ptr来访问对象,而必须调用lock()。此函数检查weak_ptr指向的对象是否仍存在,如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。
if(auto np = wp.lock())
//进入if条件证明wp指向的对象存在,在这里np与p共享对象
以上是关于动态内存与智能指针的主要内容,如果未能解决你的问题,请参考以下文章