C++动态内存

动态内存

动态内存与智能指针

  • C++中使用newdelete进行动态内存的管理,但是有时候难以确保内存分配或释放的准确性。针对这个问题,标准库中提供了智能指针,可以自动释放所指的对象,shared_ptr允许多个指针指向同一个对象。

shared_ptr

  • 进行拷贝或者赋值时,每个shared_ptr会记录有多少个其他的shared_ptr指向相同的对象,可以认为每个shared_ptr都有一个关联计数器,通常称为引用计数,拷贝一个shared_ptr时,引用计数会递增,一个shared_ptr对象被销毁时,其对应的引用计数会递减。
  • shared_ptr自动销毁所管理的对象是通过对象的析构函数来实现的。
  • shared_ptr也会自动释放相关联的内存。
  • 使用动态内存的一个常见原因是:允许多个对象共享相同的状态。平时常用的容器是通过拷贝的方式生成新的容器,如果允许多个容器可以共享相同的元素,则可以节省拷贝成本,这可以使用智能指针实现。
  • code

    void test()
    {
        // 使用智能指针的方式,sps会首先指向一个默认初始化的string
        shared_ptr<string> sps;
        if (sps && sps->empty())
        {
            *sps = "233";
            cout << *sps << endl;
        }
    
        shared_ptr<int> p1 = make_shared<int>(42);
        shared_ptr<string> p2 = make_shared<string>(10, 'A'); // make_shared可以提供特定类型的初始化参数
        shared_ptr<int> p3 = make_shared<int>();
        cout << *p1 << endl;
        cout << *p2 << endl;
        cout << *p3 << endl;
    
        shared_ptr<vector<string>> pstr1 = make_shared < vector<string> >(3, "2333");
        cout << pstr1.use_count() << endl; // 1次
        shared_ptr<vector<string>> pstr2 = pstr1;
        pstr2->push_back("test"); // 指向同一位置,所以修改pstr2,pstr1也会改变
        for_each(pstr1->begin(), pstr1->end(), [](const string& str){ cout << str << endl; });
        cout << pstr1.use_count() << endl; // 2次
    
    }
    

直接管理内存

  • 使用new和delete可以直接管理内存
  • new用于动态分配内存,delete用于释放内存空间
  • 在new时,如果申请内存空间失败,会报出bad_alloc的异常,因此可以使用nothrow,在申请内存空间失败时,返回空指针,而不会报出异常。
  • new与delete需要成对使用,否则申请的内存空间无法释放。
  • 使用new与delete管理内存有几个主要的问题
    • 常常忘记delete内存
    • 使用已经释放掉的对象
    • 同一块内存被释放两次
  • 鉴于上面的问题,建议在使用中使用智能指针。
  • code

    void test()
    {
        string s;
        // string *ps;
        // *ps = "233"; // 这种方法需要首先对指针进行赋值,否则会报错
    
        string *p1 = new string;
        *p1 = "test";
        cout << *p1 << endl;
        delete p1; // 必须手动释放内存
    
        int *p2 = new int(42);
        delete p2;
    
        // 如果出现了内存不够导致无法分配内存的情况,可以使用nothrow,如果分配失败,则会返回空指针
        int *p3 = new (nothrow) int(50);
        cout << *p3 << endl;
    
        const string* p4 = new string("hello");
        // *p4 = "ssss"; // 因为是const对象,无法修改其值,会报错
        delete p4;
    }
    

shared_ptr与new结合

  • 如果不对智能指针进行初始化,则它会被初始化为1个空指针,可以通过直接初始化的方式将一个内置指针转换为智能指针,注意:无法通过隐士转换的方法实现这样的初始化

  • code

    void test()
    {
        // shared_ptr<int> p1 = new int(4); // 无法通过这样的方法实现隐式转换
        shared_ptr<int> p2( new int(42) ); // 可以通过直接初始化的方式实现智能指针的初始化
        cout << *p2 << endl;
    }
    

get

  • get返回一个内置指针,指向智能指针管理的对象。
  • 不建议使用这个属性,因为有可能会使得内存空间管理发生混乱。
  • code

    void another(int *p)
    {
        shared_ptr<int> ret( p );
    }
    
    void test()
    {
        shared_ptr<int> p1( new int(5) );
        another(p1.get() );
        // p1所对应的内存空间会在another函数中被释放,下面的操作在运行的时候会出现错误
        //int num = *p1;
        //cout << num << endl;
    }
    

自定义智能指针释放时的操作

  • 智能指针可以自己调用析构函数进行资源释放,我们也可以指定这个智能指针对象的删除操作,需要传入一个指针对象,类型即数据类型。即使是异常,也会调用自定义的函数进行资源释放
  • code

    void freed(int *p)
    {
        delete p;
        cout << "p is being deleted" << endl;
    }
    
    void test()
    {
        // 自定义析构的函数
        shared_ptr<int> p1( new int(5), freed );
        // throw out_of_range("2333"); // 即使是出现异常,也可以调用freed进行资源释放
    }
    

unique_ptr

  • 之前的shared_ptr是通过引用计数的方法,允许多个变量使用同一块地址,unique_ptr只能指向一个给定的对象,可以使用new的方式进行直接初始化
  • unique_ptr不支持拷贝与赋值
  • 可以通过release与reset的方法将指针的所有权从一个unique_ptr转化到另一个unique_ptr
  • code

    void test()
    {
        unique_ptr<int> p1(new int(4));
        cout << *p1 << endl;
        //不支持拷贝与赋值
        // unique_ptr<int> p2( p1 ); 
        // unique_ptr<int> p2 = p1;
        unique_ptr<int> p2( p1.release() ); // release:p1放弃对指针的控制权,将p1置为空,同时返回该指针
        cout << (p1 == NULL ? "p1 is NULL" : "p1 is not NULL") << endl;
        cout << *p2 << endl;
    
        p2.reset(); // 释放了p2所指的内存,同时将p2置为空
        cout << (p2 == NULL ? "p2 is NULL" : "p2 is not NULL") << endl;
    }
    

删除器

  • 默认情况下,unique_ptrshared_ptr都是使用delete释放它所指的对象;同时也可以向unique_ptr中传入删除器,但是使用方法和shared_ptr有所差别,他需要传入2个类型模板
  • code

    void freed(int *p)
    {
        delete p;
        cout << "p is being deleted" << endl;
    }
    
    void test()
    {
        // 可以使用decltype进行函数指针的声明,方便一些
        //unique_ptr<int, decltype(freed)*> p1( new int(4), freed );
        unique_ptr<int, void(*)(int*)> p1(new int(4), freed);
        cout << *p1 << endl;
    }
    

weak_ptr

  • 很少用到,这里不做介绍

动态数组

  • new分配的对象不管是单个分配的还是数组中的,都是默认初始化的,可以在定义之后,在后面加上一对空括号,实现值初始化
  • 可以动态分配一个大小为0的数组,这是合法的,但是该数组无法进行解引用的操作。
  • 释放数组可以用delete [] p的方式,如果是单个值,用delete p即可。
  • 标准库中提供了可以管理new分配的数组的unique_ptr版本,当智能指针释放时,会调用delete [] p进行数组资源释放,但是shared_ptr没有这样的操作,如果需要其管理数组,我们需要自定义删除器

    void test()
    {
        int *p1 = new int[10]; //没有初始化
        int *p2 = new int[10](); // 初始化值为0
        int *p3 = new int[10]{1,2,3,4};
        cout << *p1 << ", " << *p2 << endl;
        for (int i = 0; i < 10; i++)
            cout << p3[i] << " ";
        cout << endl;
    
        int *p4 = new int[0](); // 初始化大小为0没有问题
        //cout << *p4 << endl; //在这里解引用会出现意想不到的结果
    
    
        delete[] p1;
        delete[] p2;
        delete[] p3;
        delete[] p4;
    
    
        cout << "unique_ptr\n";
        unique_ptr<int[]> p5(new int[10]{1,2,3,4,5,6});
        for (int i = 0; i < 10; i++)
            cout << p5[i] << " ";
        cout << endl;
    
        cout << "shared_ptr\n";
        shared_ptr<int> p6(new int[10]{8, 7, 6, 5, 4}, [](int *p){ 
            cout << "user defined delete func for shared_ptr" << endl;
            delete[] p; });
        for (int i = 0; i < 10; i++)
            cout << *(p6.get()+i) << " "; // 没有实现上面的下标引用访问的方式
        cout << endl;
    }
    

猜你喜欢

转载自blog.csdn.net/u012526003/article/details/80210458