c++核心技术09(智能指针)

为什么要用智能指针

  为了降低内存泄漏的风险,所以提出了智能指针的概念;智能指针就是通过这个原理来解决指针自动释放的问题!
  C++98提供了auto_ptr模板的解决方案
  C++11增加了unique_ptr、shared_ptr和weak_ptr


auto_ptr使用详解(c++98)

  auto_ptr是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete来释放内存!

用法:
头文件: #include
用法: auto_ptr<类型> 变量名(new 类型)
例如:
auto_ptr str(new string(“我要成为大牛~”));
auto_ptr<vector> av(new vector(10));

代码如下:

#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
#include <memory>

using namespace std;

//auto_ptr< Test > t(new Test())//忠告1:尽可能不要将auto_ptr变量定义为全局变量

class Test
{
public:
    Test() {
        cout << "Test is construct" << endl;
        debug = 1;
    }
    ~Test() { cout << "Test is destruct" << endl; }

    int getDebug() {
        return debug;
    }
private:
    int debug;
};

class FileNotFoundException : public std::exception {
public:
    const char* what() const throw() {
        return "文件不存在";
    }
};

//用法:auto_ptr<类型> 变量名(new 类型)

void memory_leak_demo1()
{
    auto_ptr< Test > t(new Test());

    //忠告3:除非自己知道后果,不要把auto_ptr智能指针赋值给同类型的另外一个智能指针
//    auto_ptr<Test> t1;
//    t1 = t;

    //Test* t = new Test() ;

    //auto_ptr<Test>* tp = new auto_ptr<Test>(new Test()); //忠告2:尽可能不要将auto_ptr变量定义为指针

    //在使用智能指针访问对象时,使用方式和普通指针一样
    cout << "-> debug:" << t->getDebug() << endl;
    cout << "* debug:" << (*t).getDebug() << endl;

//    Test* tmp = t.get();
//    cout << "get debug:" << t->getDebug() << endl;

//    Test* tmp = t.release();//release 取消智能指针对动态内存的托管,之前分配的内存必须手动释放
//    delete tmp;

    //reset重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
    //t.reset();
    t.reset(new Test());//先创建一个新的对象,然后原来的被析构掉,然后自己的生命周期结束之后,自己会被析构掉

    if(0){
        Test* t1 = new Test();
        t1->getDebug();
    }

    return;
}

int memory_leak_demo2()
{
   // Test* t = new Test();
    auto_ptr< Test > t(new Test());
    /*
     * 程序执行一段复杂的逻辑,假设尝试从一个必须存在
     * 的文件中读取某些数据,而文件此时不存在
     */

     {
        //throw exception("文件不存在");
        throw FileNotFoundException();

     }
     //delete t;

     return 0;
}

int main() {
    memory_leak_demo1();
    try {
        memory_leak_demo2();
    }
    catch(const exception& e) {
        cout <<"catch exception:" << e.what() << endl;
     }
    return 0;
}

使用建议:

  1. 尽可能不要将auto_ptr变量定义为全局变量(因为定义的对象要等程序全部结束以后才能被析构,达不到我们的目的,没有意义)或者指针(因为这样不能被析构);
  2. 除非自己知道后果,不要把auto_ptr智能指针赋值给同类型的另外一个智能指针
  3. c++11后auto_ptr已经被“抛弃”,已使用unique_ptr替代

unique_ptr的用法详解(c++11)

  auto_ptr是用于c++ 11之前的智能指针。由于auto_ptr基于排他所有权模式;两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr主要有两大问题:
  复制和赋值会改变资源的所有权。
  在STL容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
  不支持对象数组的操作。
  参考代码如下:

#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>

using namespace std;

int main() {
    //弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
    // 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
    auto_ptr<string> p1(new string("I'm martin."));
    auto_ptr<string> p2(new string("I'm rock."));
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    p1 = p2;
    printf("after p1 = p2\n");
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    //弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
    vector<auto_ptr<string>> va;
    auto_ptr<string> p3(new string("I'm p3."));
    auto_ptr<string> p4(new string("I'm p4."));

    va.push_back(std::move(p3)); //把p3右值化
    va.push_back(std::move(p4));

    cout << "va[0]: " << *va[0] << endl;
    cout << "va[1]: " << *va[1] << endl;

    //风险
    va[0] = va[1];
    cout << "va[0]: " << *va[0] << endl;
    cout << "va[1]: " << *va[1] << endl;

    //弊端3. 不支持对象数组的内存管理
    //auto_ptr<int[]> ai(new int[5]);  //不能被定义

//    //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
//    {
//        auto_ptr<string> p2;
//
//        string *str = new string("智能指针的内存管理陷阱");
//        p2.reset(str);
//        {
//            auto_ptr<string> p1;
//            p1.reset(str);
//        }
//        cout <<"str: " << *p2 << endl;
//    }
    return 0;
}

所以,C++11用更严谨的unique_ptr取代了auto_ptr!
unique_ptr特性:

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值unique_ptr复制构造,也无法进行左值赋值操作,但允许临时右值赋值构造和赋值
  3. 保存指向某个对象的指针,当他本身离开作用域时会自动释放它所指的对象
  4. 在容器中保存指针是安全的

参考代码如下:

#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>

using namespace std;

int main1() {
    //弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
    // 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
    auto_ptr<string> p1(new string("I'm martin."));
    auto_ptr<string> p2(new string("I'm rock."));
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    p1 = p2;
    printf("after p1 = p2\n");
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    //弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
    vector<auto_ptr<string>> va;
    auto_ptr<string> p3(new string("I'm p3."));
    auto_ptr<string> p4(new string("I'm p4."));

    va.push_back(std::move(p3)); //把p3右值化
    va.push_back(std::move(p4));

    cout << "va[0]: " << *va[0] << endl;
    cout << "va[1]: " << *va[1] << endl;

    //风险
    va[0] = va[1];
    cout << "va[0]: " << *va[0] << endl;
    cout << "va[1]: " << *va[1] << endl;

    //弊端3. 不支持对象数组的内存管理
    //auto_ptr<int[]> ai(new int[5]);  //不能被定义

//    //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
//    {
//        auto_ptr<string> p2;
//
//        string *str = new string("智能指针的内存管理陷阱");
//        p2.reset(str);
//        {
//            auto_ptr<string> p1;
//            p1.reset(str);
//        }
//        cout <<"str: " << *p2 << endl;
//    }
    return 0;
}

int main() {
    //弊端1. auto_ptr 被c++11 抛弃的主要理由:复制或赋值都会改变资源的所有权
    // 当一个 auto_ptr 被赋值给另一个 auto_ptr 时,所有权会从一个对象转移到另一个对象,导致源对象变为空。
    //unique_ptr如何解决这个问题?不允许显示的右值赋值和构造
    unique_ptr<string> p1(new string("I'm martin."));
    unique_ptr<string> p2(new string("I'm rock."));
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    //如果一定要转移,使用move把左值转成右值
    p1 = std::move(p2);
    printf("p1:%p\n",p1.get());
    printf("p2:%p\n",p2.get());

    //p1 = p2; //左值赋值禁止
    unique_ptr<string> p3(new string("I'm p3."));
    unique_ptr<string> p4(std::move(p3)); //左值拷贝构造也不行,必须转换成右值

    //弊端2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制(copy constructable)和可赋值(assignable)
    vector<unique_ptr<string>> vu;
    unique_ptr<string> p5(new string("I'm p5."));
    unique_ptr<string> p6(new string("I'm p6."));

    vu.push_back(std::move(p3)); //把p3右值化
    vu.push_back(std::move(p4));

    cout << "vu[0]: " << *vu[0] << endl;
    cout << "vu[1]: " << *vu[1] << endl;

    //风险
    //vu[0] = vu[1]; //将1交给0去做,1就废掉了,对于unique_ptr是显式的,但对于auto_ptr是隐式的
    cout << "vu[0]: " << *vu[0] << endl;
    cout << "vu[1]: " << *vu[1] << endl;

    //弊端3. auto_ptr不支持对象数组的内存管理
    //但是unique_ptr支持对向数组的管理
    unique_ptr<int[]> ui(new int[5]);//自动会调用delete[]函数去释放
    //auto_ptr<int[]> ai(new int[5]);  //不能被定义

//    //auto_ptr 陷阱,不能把同一段内存交给多个auto_ptr去管理
//    {
//        auto_ptr<string> p2;
//
//        string *str = new string("智能指针的内存管理陷阱");
//        p2.reset(str);
//        {
//            auto_ptr<string> p1;
//            p1.reset(str);
//        }
//        cout <<"str: " << *p2 << endl;
//    }*/
    return 0;
}

构造函数
unique_ptr< T > up; //空的unique_ptr,可以指向类型为T的对象
unique_ptr< T > up1(new T());//定义unique_ptr,同时指向类型为T的对象
unique_ptr<T[]> up;//空的unique_ptr,可以指向类型为T的数组对象
unique_ptr<T[]> up1(new T());//定义unique_ptr,同时指向类型为T的数组对象
unique_ptr<T,D> up();//空的unique_ptr,接受一个D类型的删除器d,使用d释放内存
unique_ptr<T,D> up(new T());//定义unique_ptr,同时指向类型为T的数组对象,接受一个D类型的删除器d,使用删除器释放内存

赋值
unique_ptr< int > up1(new int(10));
unique_ptr< int > up2(new int(11));
up1 = std::move(up2);//必须使用移动语义,结果up1内存释放,up2交由up1管理

主动释放对象
up = nullptr;//释放up指向的对象,将up置为空
或 up = NULL;//作用相同

放弃对象控制权
up.release;//放弃对象的控制权,返回指针,将up置为空,不会·释放内存

交换
up.swap(up1);//将智能指针up和up1管控的对象进行交换


shared_ptr使用详解(C++11)

构造函数
shared_ptr< T > sp; //空的shared_ptr,可以指向类型为T的对象
shared_ptr< T > sp1(new T()); //定义sherad_ptr,同时指向类型为T的对象
shared_ptr< T[] > sp2; //空的shared_ptr,可以指向类型为T的数组对象( c++17支持)
shared_ptr< T[] > sp3(new T[]{…}); //指向类型为T的数组对象( c++17支持)
shared_ptr< T > sp4(NULL,D()); //空的shard_ptr,接受一个D类型的删除器,使用D释放内存
shared_ptr< T > sp5(new T(),D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存

代码如下:

#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>

using namespace std;

class Person
{
public:
    Person(int _no) : no(_no)
    {
        cout << "Construct " << no <<  endl;
    }
    ~Person()
    {
        cout << "Destruct " << no << endl;
    }
    int no;
};

class DestructPerson
{
public:
    void operator()(Person* pt)
    {
        cout << " DestructPerson..." << endl;
        delete pt;
    }
};

int main()
{

    shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
    shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象

    cout << "sp1 ref_counter:" << sp1.use_count() << endl;
    cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.

    sp1 = sp2;
    cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;

    shared_ptr<Person> sp3(sp1);
    cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针

    //数组对象的管理
    shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]

    shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象

    

    return 0;
}

初始化

方式一  构造函数
shared_ptr< int > up1(new int(10));// int(10)的引用计数为1
shared_ptr< int > up2(up1);//使用智能指针up1构造up2,此时int(10)引用计数为2

方式二 使用make_shared初始化对象,分配内存效率更高
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr;
用法:
make_shared< 类型 >(构造类型对象需要的参数列表)
shared_ptr< int > p4 = make_shared< int >(2);// 多个参数以逗号’,'隔开,最多接受十个
shared_ptr< string > p4 = make_shared< string >(“字符串”);

参考代码如下:

#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>

using namespace std;

class Person
{
public:
    Person(int _no) : no(_no)
    {
        cout << "Construct " << no <<  endl;
    }
    ~Person()
    {
        cout << "Destruct " << no << endl;
    }
    int no;
};

class DestructPerson
{
public:
    void operator()(Person* pt)
    {
        cout << " DestructPerson..." << endl;
        delete pt;
    }
};

int main()
{

    shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
    shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象

    cout << "sp1 ref_counter:" << sp1.use_count() << endl;
    cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.

    sp1 = sp2;
    cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;

    shared_ptr<Person> sp3(sp1);
    cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针

    //数组对象的管理
    shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]

    shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象

    //使用 make_ptr 函数模板初始化
    shared_ptr<Person> sp9;
    sp9 = make_shared<Person>(9);
    cout << "after sp9 = make_shared<Person>(9),sp9 ref_counter:" << sp9.use_count() << endl;// 当前管控Person(2)的共享指针的数量.

    return 0;
}

赋值
shared_ptr< int > up1(new int(10));// int(10)的引用计数为1
shared_ptr< int > up2(new int(11));// int(11)的引用计数为1
up1 = up2;//int(10)的引用计数减1,计数归零内存释放,up2共享int(11)给up1,int(11)的引用计数为2

主动释放对象
shared_ptr< int > up1(new int(10));
  up1 = nullptr;//int(10)的引用计数减1,计数归零后内存释放
 或
  up1 = NULL;//作用同上

重置
up.reset(); //将up重置为空指针,所管理对象引用计数 减1
up.reset(p1);//将up重置为p1(的值),up管控的对象计数减1,up接管对p1指针的管控
up.reset(p1,d);//将up重置为p1(的值),up管控的对象计数减1,并使用d作为删除器

交换
std::swqp(p1,p2); //交换p1和p2管理的对象,原对象的引用计数不变
p1.swap(p2);//同上

代码如下:

#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>

using namespace std;

class Person
{
public:
    Person(int _no) : no(_no)
    {
        cout << "Construct " << no <<  endl;
    }
    ~Person()
    {
        cout << "Destruct " << no << endl;
    }
    int no;
};

class DestructPerson
{
public:
    void operator()(Person* pt)
    {
        cout << " DestructPerson..." << endl;
        delete pt;
    }
};

int main()
{

    shared_ptr<Person> sp1; //空的share_ptr,可以指向类型为T的对象
    shared_ptr<Person> sp2(new Person(2));//定义shared_ptr,同时指向类型为T的对象

    cout << "sp1 ref_counter:" << sp1.use_count() << endl;
    cout << "sp2 ref_counter:" << sp2.use_count() << endl;// 当前管控Person(2)的共享指针的数量.

    sp1 = sp2;
    cout << "after sp1 = sp2, sp2 ref_counter:" << sp2.use_count() << endl;

    shared_ptr<Person> sp3(sp1);
    cout << "after sp3(sp1), sp2 ref_counter:" << sp3.use_count() << endl;//当前管控Person(2)的共享指针

    //数组对象的管理
    shared_ptr<Person[]> sp4(new Person[5]{3,4,5,6,7}); //delete[]

    shared_ptr<Person> sp8(new Person(8), DestructPerson());//调用 DestructPerson() 释放对象

    //使用 make_ptr 函数模板初始化
    shared_ptr<Person> sp9;
    sp9 = make_shared<Person>(9);
    cout << "after sp9 = make_shared<Person>(9),sp9 ref_counter:" << sp9.use_count() << endl;// 当前管控Person(2)的共享指针的数量.

    //重置
//    shared_ptr<Person> sp10;
//    Person* p10 = new Person(10);
//    sp9.reset(p10);
//    cout << "after sp9.reset(p10),sp9 ref_counter:" << sp9.use_count() << endl;

    shared_ptr<Person> sp10 = sp9;
    Person* p10 = new Person(10);
    sp9.reset(p10);
    cout << "after sp9.reset(10),sp9 ref_counter: " << sp9.use_count() << endl;
    std::swap(sp9,sp10);

    cout << "sp9.swap(sp10) 交换后,sp9: " << sp9->no << " sp10: " << sp10->no << endl;

    return 0;
}

使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

代码如下:

#include <iostream>
#include <memory>
#include <stdio.h>
#include <vector>

using namespace std;

class girl;

class boy
{
public:
    boy()
    {
        cout << "boy construct!" << endl;
    }
    ~boy()
    {
        cout << "boy destruct!" << endl;
    }
    void set_girl_friend(shared_ptr<girl>& g)
    {
        girl_friend = g;
    }

private:
    shared_ptr<girl> girl_friend;
};

class girl
{
public:
    girl()
    {
        cout << "girl construct!" << endl;
    }
    ~girl()
    {
        cout << "girl destruct!" << endl;
    }
    void set_boy_friend(shared_ptr<boy>& b)
    {
        boy_friend = b;
    }

private:
    shared_ptr<boy> boy_friend;
};


void use_trap()
{
    shared_ptr<girl> sp_girl(new girl()); //白娘子
    shared_ptr<boy> sp_boy(new boy()); //许仙

    sp_girl->set_boy_friend(sp_boy);
    sp_boy->set_girl_friend(sp_girl);

}

int main() {
    use_trap();
    return 0;
}

weak_ptr

  weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助工作,主要用于解决共享指针可能引发的循环引用问题,而循环引用指的是两个或多个对象相互持有对方的共有指针,导致它们无法被正确地释放,从而造成内存泄漏,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起记数的增加或减少,同时weak_ptr没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。

代码如下:

#include <iostream>
#include <memory>
#include <stdio.h>
#include <vector>

using namespace std;

class girl;

class boy
{
public:
    boy()
    {
        cout << "boy construct!" << endl;
    }
    ~boy()
    {
        cout << "boy destruct!" << endl;
    }
    void set_girl_friend(shared_ptr<girl>& g)
    {
        girl_friend = g;
    }

private:
    shared_ptr<girl> girl_friend;
};

class girl
{
public:
    girl()
    {
        cout << "girl construct!" << endl;
    }
    ~girl()
    {
        cout << "girl destruct!" << endl;
    }
    void set_boy_friend(shared_ptr<boy>& b)
    {
        boy_friend = b;
    }

private:
    shared_ptr<boy> boy_friend;
};


void use_trap()
{
    shared_ptr<girl> sp_girl(new girl()); //白娘子
    shared_ptr<boy> sp_boy(new boy()); //许仙

    //弱指针的使用
    weak_ptr<girl> wp_girl; //定义空的弱指针
    weak_ptr<girl> wp_girl2(sp_girl); //使用共享构造
    wp_girl = sp_girl; //允许共享指针赋值给弱指针

    cout << "sp_girl ref_count:" << sp_girl.use_count() << endl;
    cout << "wp_girl.use_count:" << wp_girl.use_count() << endl;

    //(*sp_girl).set_boy_frined(sp_boy); 弱指针不支持* 和 ->对指针访问
    //sp_girl->set_boy(sp_boy);

    //在必要的时候可以转成共享指针
    shared_ptr<girl> sp_girl1;
    sp_girl1 = wp_girl.lock();
    cout << "after locked,wp_girl.use_count:" << wp_girl.use_count() << endl;

    sp_girl1 = NULL;

    sp_girl->set_boy_friend(sp_boy);
    //sp_boy->set_girl_friend(sp_girl);

}

int main() {
    use_trap();
    return 0;
}

  根据上述代码可知,假如我们不使用弱指针,将会产生循环引用问题,如下图所示:
在这里插入图片描述
  如图所示,由于他们互相持有对方的共享指针,导致它们的引用计数无法变为零,从而无法释放内存,形成了循环引用,这个时候就需要weak_ptr来解决这个问题。
  并且我们可以使用weak_ptr的lock()成员函数来获取一个可用的shared_ptr对象,该函数返回一个shared_ptr,指向weak_ptr所指的对象(如果对象仍然存在),否则返回一个空的shared_ptr,他在处理循环引用时可以检查对象的有效性。

  • 23
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值