即日起在codingBlog上分享您的技术经验即可获得积分,积分可兑换现金哦。

动态内存与智能指针

编程语言 the_scent_of_th_soul 18℃ 0评论
本文目录
[隐藏]

我们先来看一些对象的生存期。全局对象在程序启动时分配,在程序结束时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。局部自动对象,在进入其定义所在的程序块儿时被创建,离开块时销毁。即,它们都是由编译器自动创建与销毁。


而动态分配的对象的生存期与它们在哪里创建的无关,只有当显式地被释放时,这些对象才销毁。

在C++中,动态内存的管理是通过一对运算符来完成的:

new:在动态内存为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化。
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

1.动态内存的使用很容易出问题,这里列举如下几点:

缓冲区溢出
空悬指针/野指针
重复释放: 释放已经被释放过了的内存
内存泄漏: 忘记释放内存
不配对的new[]/delete

先列这几条,等先了解智能指针的概念以后在来看怎么用智能指针解决这些问题。

2.为了更安全(同时也更容易)地使用动态内存,标准库提供了智能指针类型来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。

我们首先来看一下shared_ptr类,该类型在memory头文件中。

3.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
auto p6 = make_shared<vector<string>>();

4.shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象。

auto p = make_shared<int>(42);   //p指向的对象只有p一个引用者
auto q(p);                       //pq指向相同的对象,此对象有两个引用者。

可以认为每个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的析构函数就会销毁对象,并释放它占用的内存。

5.对于一块内存,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
#include
#include
#include

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;
}

6.其他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

7.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共享对象
}

转载请注明:CodingBlog » 动态内存与智能指针

喜欢 (0)or分享 (0)
发表我的评论
取消评论

*

表情