C++动态内存和智能指针

每个程序都拥有一个被称为自由空间或者堆的内存池,用来存储动态分配的对象即程序在运行过程中分配的对象。需要明确一点,当我们在不再需要动态分配的对象时,需要显示的去释放其所占空间,不然会造成内存泄漏

在C++中,动态内存的管理是通过newdelete完成。

new:  在动态内存中为对象分配空间,并返回指向该对象的指针
delete: 接受一个动态对象的指针,销毁对象,并释放与之相关联的内存

先看一个动态内存分配的例子

void example(std::string & str)
{
    std::string * pstr = new std::string(str); //动态分配一个字符串对象并使用str初始化
    ...
    if (something_error())   //若有异常
    {
        throw exception();
    }
    str = *pstr;
    delete pstr;
    return;
}

当这个程序有异常发生时,将抛出异常并终止程序,并且造成内存泄漏。导致这样的原因是pstr指针指向的空间没有释放。
想到的解决办法:可以在异常处理块里面加入delete。但是,若程序很庞大,总是会忘记在适当的地方加入delete手动释放空间。

程序员是懒惰的,总是想着一切能省事就省事的解决办法。对于管理动态内存空间一样,当我们动态创建了一个对象,可不可以有一个方法不用我们手动去管理这个对象的生存而是自动地管理这个对象,当在该对象不再被使用时,自动释放该对象所占内存空间?

这个简便方法的描述很像是对析构函数的描述,若一个对象有析构函数,当该对象过期了,那么它的析构函数便会释放它所占空间进行善后处理。这里的pstr只是一个普通的指针不具有析构函数,但若pstr是一个类对象指针,那就可以解决我们上述的问题。当该指针对象过期时,就可以在其析构函数里添加方法,让它的析构函数释放它所指向的空间

可以总结一下:将基本类型的指针封装为类对象指针(这里的类是一个模板类,可以适用于不同类型),并在析构函数里编写delete语句删除指针指向的内存空间。

以上便是C++中智能指针的设计思想。

下面简要介绍一下C++中智能指针。
新的STL(标准模板库)提供了两种智能指针类型来管理动态对象。分别为shared_ptr、unique_ptr。早期的C++98提供了auto_ptr但现在已被摒弃。
新标准中还提供了weak_ptr的伴随类,一种弱引用,指向shared_ptr所指对象,这里暂不讨论它。
要注意使用智能指针都要包含memory头文件。

新标准中这两种智能指针的主要区别在于管理底层指针的方式

  • shared_ptr
    允许多个shared_ptr指向同一对象,采用“引用计数”的方式,表示有多少指针指向该对象,当引用计数为0时,该对象会被释放
  • unique_ptr
    “独占”所指向的对象。这里有一个所有权的概念,对于特定的对象,只能有一个智能指针去指向,然后该智能指针的析构函数可以删除该对象。赋值操作可以转让所有权。


在使用智能指针时需要注意,所有的智能指针类的构造函数带有explicit关键字,所以我们不能将内置指针隐式地转换为一个智能指针,必须使用直接初始化形式初始化一个智能指针

shared_ptr<int> p1 = new int(1024);     //错误,存在隐式转换 int * 转换为  shared_ptr<int>
shared_ptr<int> p2(new int(1024));      //正确,使用了直接初始化形式


默认情况下,一个用于初始化智能指针的普通指针必须指向动态内存,若普通指针是指向栈内存或者其他非堆内存,那么在智能指针过期时,会使用delete释放与之相关联的对象,此时就会作用在非堆内存,会导致错误。
但是若必须这样做,就需要提供自己的删除器代替delete,在定义智能指针时就要使用,shared_ptr< T >p (q,d) (p接管智能指针q所指向的对象的所有权,q是可以转换为T*类型,p将调用对象d代替delete)。


下面是对智能指针的使用举例

#include <iostream>
#include <cstdlib>
#include <string>
#include <memory>
using namespace std;

class example
{
public:
    example(const string & s) :str(s) { cout << "constructing..." << endl; }
    ~example() { cout << "destructing..." << endl; }
    void show() { cout << str << endl; }
private:
    string str;
};

int main(int argc,char * argv[])
{
    //像是使用普通指针一样使用智能指针
    shared_ptr<example> pshared(new example("this is shared_ptr"));
    pshared->show();

    unique_ptr<example> punique(new example("this is unique_ptr"));
    punique->show();
}

shared_ptr

使用智能指针shared_ptr时,尽可能地使用make_shared标准库函数分配动态内存和auto定义一个对象来保存make_shared返回的对象。auto会自动识别对象类型。

若是直接使用普通指针去初始化智能指针,可能会导致同一块内存被释放两次

int main(int argc,char * argv[])
{
    example * p = new example();
    shared_ptr<example> p1(p);
    shared_ptr<example> p2(p);
}

因为是使用普通指针初始化两个智能指针,所以对象的引用计数为1。当p1失效时,会释放其指向的内存,并且p2失效时也会继续释放该内存!

所以避免这种情况使用make_shared比较好!

int main(int argc,char * argv[])
{
    auto p1 = make_shared<example>();
    //atuo p2(p1);
    shared_ptr<example>p2(p1); //使用智能指针初始化智能指针
    //shared_ptr<example> p2 = p1;  //shared_ptr也支持赋值初始化
}

unique_ptr

前面说过,早期的C++中提供了auto,并且auto与unique_ptr都是采用“所有权”的制度,“独占”对象的。后面auto_ptr之所以会被摒弃,是因为auto_ptr会造成潜在的内存崩溃问题。

    auto_ptr<string> p1(new string("example"));
    auto_ptr<string> p2;
    p2 = p1;

p2会接管p1所指对象的接管权,该对象空间不会被释放
但是,若在后续过程中继续使用p1,那么便会出错,因为此时p1所指数据不再有效!

若是使用unique_ptr:

    unique_ptr<string> p1(new string("example"));
    uniq_ptr<string> p2;
    p2 = p1; //写到这条语句时,编译器会立即报错,认为该语句不合法,这样便杜绝p1所指不明确

    p2 = move(p1);//可以使用move,将p1所指对象的所有权转给p2,而p1变成空指针,对p1重新赋值
    p1 = make_unique<exmaple>();//同make_shared()函数一样,最好使用make_unique返回unique_ptr对象

可以看出,unique_ptr是优越于auto_ptr的。

unique不像shared_ptr支持普通的拷贝或赋值操作,例如下面的情况都是不允许的:

    unique_ptr<example>p1(new example("hello"));
    unique_ptr<example>p2(p1);  //error
    unique_ptr<example>p3 = p1; //error

但是有一种情况除外:
若unique_ptr是一个将要被销毁的unique_ptr,或者说是它是一个临时右值,在以后都没有可能继续使用,那么就可以将它赋值给另外一个unique_ptr。否则,编译器将会报错。

...
unique_ptr<exmaple> fun(const string & str)
{
    return unique_ptr<example>(new example(str));
}
...
int main(int argc,char * argv[])
{
    ...
    unique_ptr<example> p = fun("hello");
    ...
}

在函数fun返回了一个临时的unique_ptr,在主函数中p接管了临时unique_ptr所指对象的所有权,然后临时的unique_ptr被销毁,以后也不可能用到这个临时unique_ptr,所以这种赋值是不会有问题的。

还需要提一点,在智能指针类中,unique_ptr是唯一可以适用于C语言中数组的指针。

    auto p = make_unique<int[]>(10);
    p[5] = 7;

在自动管理内存时可能会存在以下缺点:
1. 忘记释放内存
2. 使用已经释放掉的对象
3. 同一块内存释放两次
4. delete之后忘记重置指针

所以在使用动态内存手动管理时要注意这些!但最好还是使用智能指针!

猜你喜欢

转载自blog.csdn.net/RENZHADEBENYUAN/article/details/80569570