day10(C++)智能指针

目录

智能指针

1. 概念

2. auto_ptr 自动指针

3. unique_ptr 唯一指针

4. shared_ptr

5. weak_ptr

6. 窥探源码(了解)


智能指针

1. 概念

C++堆内存对象在new之后使用,如果忘记delete则会产生内存泄漏的问题。

诸如Java、C#等语言直接提供垃圾回收机制来处理不使用的对象,因此在C++98中引入了智能指针的概念,并在C++11中趋于完善。

使用智能指针可以让堆内存对象不调用delete就能被销毁。

智能指针的原理是通过一个栈内存的智能指针对象,控制被管理的堆内存对象生命周期,当栈内存的智能指针对象销毁时,在析构函数中释放被管理管理的堆内存对象。这样程序员就不需要手动调用delete释放堆内存对象了。

C++中有四种只能指针:

  • auto_ptr 自动指针(C++98),已废弃。
  • unique_ptr 唯一指针(C++11)
  • shared_ptr 共享指针(C++11)
  • weak_ptr 虚指针(C++11)

智能指针的使用需要引入头文件 #include <memory>

2. auto_ptr 自动指针

C++98的智能指针,目前已经不推荐使用。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    Source* src = new Source("A");
    // 创建一个智能指针对象ap1,管理对象A
    auto_ptr<Source> ap1(src);
    // 上面的操作可以一步完成
    auto_ptr<Source> ap2(new Source("B"));
    // 被管理的对象可以通过getter拿出来
    ap2.get()->show(); // B
    src = ap2.get();
    src->show(); // B
//    delete src; 乱码:对象内存不能释放两次
    // 解除ap1对A的管理,发返回值为对象A
    src = ap1.release();
    src->show(); // A
    delete src; // 因为A回到手动模式,所以可以delete销毁
    // 解除ap2对B的管理,同时销毁B
    ap2.reset();
    auto_ptr<Source> ap3(new Source("C"));
    // 使用新的资源对象D替代C,C被销毁
    ap3.reset(new Source("D"));

    cout << "主函数结束" << endl;
    return 0;
}

auto_ptr真正容易误用的地方是其不常用的复制语义(拷贝构造函数和赋值运算符),与浅拷贝的区别是,auto_ptr的复制语义不会让多个auto_ptr共享一个资源,而是会出现控制权的转移

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    auto_ptr<Source> ap1(new Source("A"));
    cout << ap1.get() << endl; // 0xfa1060
    auto_ptr<Source> ap2(ap1); // 拷贝构造函数
    cout << ap1.get() << " " << ap2.get() << endl; // 0 0xfa1060
    auto_ptr<Source> ap3;
    ap3 = ap2; // 赋值运算符
    cout << ap1.get() << " " << ap2.get()
         << " " << ap3.get() << endl; // 0 0 0xfa1060

    cout << "主函数结束" << endl;
    return 0;
}

3. unique_ptr 唯一指针

作为对auto_ptr的改进,unique_ptr对其持有的堆内存对象具有唯一控制权,即不可以转移到其他智能指针管理。

上面的代码中可以看到,所有复制语义都被屏蔽了。

unique_ptr可以增加move函数达到之前控制权转移的效果

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    unique_ptr<Source> up1(new Source("A"));
    cout << up1.get() << endl; // 0xfa1060
    unique_ptr<Source> up2(move(up1)); // 拷贝构造函数
    cout << up1.get() << " " << up2.get() << endl; // 0 0xfa1060
    unique_ptr<Source> up3;
    up3 = move(up2); // 赋值运算符
    cout << up1.get() << " " << up2.get()
         << " " << up3.get() << endl; // 0 0 0xfa1060
    // 如果两个unique_ptr对象交换资源,可以使用swap函数
    unique_ptr<Source> up4(new Source("B"));
    unique_ptr<Source> up5(new Source("C"));
    up4.swap(up5);
    up4.get()->show();
    up5.get()->show();

    cout << "主函数结束" << endl;
    return 0;
}

4. shared_ptr

shared_ptr可以把持有的资源在多个智能指针之间共享。

shared_ptr除了可以使用构造函数创建外,还可以使用make_shared函数创建。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    shared_ptr<Source> sp1(new Source("A"));
    shared_ptr<Source> sp2 = make_shared<Source>("B");

    cout << "主函数结束" << endl;
    return 0;
}

make_shared函数创建相比于普通的构造函数:

  • 性能更好
  • 更加安全
  • 可能内存延迟释放

更推荐使用make_shared函数创建shared_ptr对象。

每个shared_ptr内部都多一个隐藏的成员变量,这个变量通常被称为“引用计数”,每多一个shared_ptr持有资源就计数+1,每少一个shared_ptr持有资源就计数-1,当计数从1变为0时,释放资源。

持有相同资源的shared_ptr对象,引用计数一定相同。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    shared_ptr<Source> sp1 = make_shared<Source>("A");
    cout << sp1.use_count() << endl; // 1
    shared_ptr<Source> sp2(sp1); // 拷贝构造函数
    cout << sp1.use_count() << " " << sp2.use_count() << endl; // 2 2
    sp2.reset(); // 解除sp2对A的管理
    cout << sp1.use_count() << " " << sp2.use_count() << endl; // 1 0
    shared_ptr<Source> sp3;
    sp3 = sp1; // 赋值运算符
    cout << sp1.use_count() << " " << sp3.use_count() << endl; // 2 2
    shared_ptr<Source> sp4 = sp3; // 拷贝构造函数
    cout << sp1.use_count() << endl; // 3
//    shared_ptr<Source> sp5(sp4.get()); 错误
    sp4.get()->show();
    { // 局部代码块
        shared_ptr<Source> sp6;
        sp6 = sp1;
        cout << sp1.use_count() << endl; // 4
    }
    cout << sp1.use_count() << endl; // 3

    cout << "主函数结束" << endl;
    return 0;
}

5. weak_ptr

weak_ptr本身无法独立工作,但是可以配合shared_ptr进行工作,weak_ptr对资源对象的管理是一种“弱引用”,因为不会影响引用计数。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    shared_ptr<Source> sp1 = make_shared<Source>("A");
    weak_ptr<Source> wp1 = sp1;
    cout << sp1.use_count() << endl; // 1
    cout << wp1.use_count() << endl; // 1
//    wp1.get(); 无法获取资源
    sp1.reset(); // 计数1→0,对象A销毁
    cout << wp1.use_count() << endl; // 0

    cout << "主函数结束" << endl;
    return 0;
}

可以使用expired函数检测虚指针对资源持有的有效性,即资源对象是否已销毁。如果资源对象没有被销毁,可以使用lock函数生成一个shared_ptr对象共享资源。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    shared_ptr<Source> sp1 = make_shared<Source>("A");
    weak_ptr<Source> wp1 = sp1;
//    sp1.reset();
    shared_ptr<Source> sp2;
    // lock能用的前提是资源对象还在
    if(wp1.expired())
    {
        cout << "资源已失效" << endl;
    }else
    {
        sp2 = wp1.lock(); // 返回一个shared_ptr<Source>,但是是临时的,需要保存下来
        cout << sp1.use_count() << " " <<
                sp2.use_count() <<" " << wp1.use_count() << endl; // 2 2 2
    }

    cout << "主函数结束" << endl;
    return 0;
}

6. 窥探源码(了解)

经过上述学习可以得到结论:智能指针使用模板实现,堆内存对象和引用计数分别是智能指针对象的成员变量,可以通过sizeof观察到。

#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

int main()
{
    auto_ptr<Source> ap1(new Source("A"));
    cout << sizeof(ap1) << endl; // 4

    unique_ptr<Source> up1(new Source("N"));
    cout << sizeof(up1) << endl; // 4

    shared_ptr<Source> sp1 = make_shared<Source>("S");
    cout << sizeof(sp1) << endl; // 8

    weak_ptr<Source> wp1 = sp1;
    cout << sizeof(wp1) << endl; // 8

    return 0;
}

下面手搓一个简化版的SharedPtr类,简单了解一下智能指针的内部构造,包含以下功能:

  • 构造函数
  • 拷贝构造函数
  • 赋值运算符
  • get函数
  • use_count函数
  • reset函数
  • 析构函数
#include <iostream>
#include <memory> // 头文件

using namespace std;

/**
 * @brief The Source class
 * 被智能指针管理的堆内存资源对象类
 */
class Source
{
private:
    string name;

public:
    Source(string name):name(name)
    {
        cout << name << "构造函数" << endl;
    }

    ~Source()
    {
        cout << name << "析构函数" << endl;
    }

    void show()
    {
        cout << name << "成员函数" << endl;
    }
};

template <class T>
class SharedPtr
{
private:
    T* res = NULL;
    int* count = NULL;

public:
    // 构造函数
    SharedPtr(T* t):res(t),count(new int(1)){}

    SharedPtr(const SharedPtr& sp):res(sp.res),count(sp.count)
    {
        (*count)++; // 因为使用的是拷贝构造函数,因此计数+1
    }

    SharedPtr& operator =(const SharedPtr& sp)
    {
        if(this != &sp) // 避免自赋值
        {
            // 如果之前有资源
            // 如果计数>1,计数-1
            // 如果计数=1,计数-1,变0,销毁
            reset();
            res = sp.res;
            count = sp.count;
            (*count)++;
        }
        return *this;
    }

    void reset()
    {
        // 如果之前有资源
        if(res != NULL)
        {
            // 计数-1
            (*count)--;
            if(*count == 0) // 减完之后如果计数为0
            {
                delete res;
                delete count;
            }
            res = NULL;
            count = NULL;
        }
    }

    T* get() const
    {
        return res;
    }

    int use_count() const
    {
        return *count;
    }

    ~SharedPtr()
    {
        reset();
    }
};

int main()
{
    SharedPtr<Source> sp1(new Source("A"));
    SharedPtr<Source> sp2(new Source("B"));
    sp2 = sp1; // A销毁,sp2持有B

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值