C++ 动态分配内存&智能指针

前言

本文介绍C++中的内存动态分配、和更好的用于对象内存管理的智能指针。

动态分配内存

  • 对象生命周期
    全局对象在程序启动时分配,在程序结束时销毁。
    局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。
    局部static对象在第一次使用前分配,在程序结束时销毁。
    动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

  • 分配在静态或栈内存中的对象由编译器自动创建和销毁。
    静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量;static对象在使用之前分配,在程序结束时销毁。
    栈内存用来保存定义在函数内的非static对象,对于栈对象,仅在其定义的程序块运行时才存在。

  • 分配在堆内存中的对象由程序来控制其创建和销毁
    用堆来存储动态分配(dynamically allocate)的对象——即,那些在程序运行时分配的对象,在堆(heap)内存上分配。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。

  • 动态内存的管理是通过一对运算符来完成的:
    new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;
    delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

  • 新标准库引入了智能指针:
    shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

shared_ptr

  • 智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。

  • 智能指针使用基本规范:

    • 不要混合使用普通指针和智能指针
    • 不要使用get()初始化另一个智能指针或为智能指针赋值
    • 不使用相同的内置指针值初始化(或reset)多个智能指针。
    • 不delete get()返回的指针。
    • 不使用get()初始化或reset另一个智能指针。
    • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
    • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

初始化

可以使用make_shared或直接初始化初始化一个智能指针。

    {
        // 原始指针使用方式
        MyString *p;
        p = new MyString("p"); // 单参构造函数
        delete p; // p~析构函数
        p = nullptr; // 一定要重置为nullptr,减少出错

        shared_ptr<MyString> p1; // 声明一个指针,未进行初始化时他是一个空指针,直接使用会npe
        if (p1 == nullptr) {
            cout << "npe" << endl; // npe,声明一个指针的时候默认值是nullptr
        }

        shared_ptr<MyString> p01 = shared_ptr<MyString>(new MyString("p01")); // 单参构造函数,
        shared_ptr<MyString> p02 = make_shared<MyString>("p02"); // 单参构造函数,给该智能指针赋值
//        shared_ptr<MyString> p11 = new MyString("p11"); // 错误,因为shared_ptr禁用了隐式类型转换
        shared_ptr<MyString> p03(new MyString("p03")); // 正确,直接初始化,单参构造函数
    }
//    p03~析构函数
//    p02~析构函数
//    p01~析构函数

拷贝赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时我们拷贝一个shared_ptr,计数器都会递增。

    {
        auto p1 = make_shared<MyString>("p1"); // 单参构造函数,给该智能指针赋值
        cout << p1 << ", " << *p1 << ", " << p1.use_count() << endl; // 0x6000012544b8, p1, 1

        shared_ptr<MyString> p2 = p1; // 当进行拷贝赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
        cout << p2 << ", " << *p2 << ", " << p1.use_count() << endl; // 0x6000012544b8, p1, 2

        auto p3 = make_shared<MyString>("p3");
        p3 = p1; // p3~析构函数,拷贝赋值会将原先的share_ptr持有的对象释放
        cout << p3 << ", " << *p3 << ", "
             << p1.use_count() << ", " << p3.use_count() << endl; // 0x6000012544b8, p1, 3, 3

        shared_ptr<MyString> p4(p3); // 当进行拷贝构造操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
        cout << p4 << ", " << *p4 << ", "
             << p1.use_count() << ", " << p3.use_count() << ", " << p4.use_count()
             << endl; // 0x6000012544b8, p1, 4, 4, 4
    }
    // p1~析构函数

unique_ptr

一个unique_ptr“拥有”它所指向的对象。
与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。
当unique_ptr被销毁时,它所指向的对象也被销毁。
没有类似make_shared的标准库函数返回一个unique_ptr。
unique_ptr不支持普通的拷贝或赋值操作。

    {
        unique_ptr<MyString> p1;
        if (p1 == nullptr) {
            cout << "npe" << endl; // npe
        }
        unique_ptr<MyString> p2(new MyString("p2")); // 直接初始化,unique_ptr也禁用了隐式类型转换,单参构造函数

        // unique_ptr无法拷贝构造和拷贝赋值
//        unique_ptr<MyString> p3(p2); // 错误,不支持拷贝
//        unique_ptr<MyString> p3 = p2; // 错误,不支持赋值

        // 可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique
        unique_ptr<MyString> p3(p2.release()); // release将p2置为空
        cout << p2 << "," << p3 << ", " << *p3 << endl; // 0x0,0x600000f58000, p2
    }
    // p2~析构函数

weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。
    将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
    一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放;即使有weak_ptr指向对象,对象也还是会被释放。

  • 不能使用weak_ptr直接访问对象,而必须调用lock。
    此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。

    weak_ptr<MyString> pOuter;
    {
        auto p1 = make_shared<MyString>("p1");
        pOuter = p1;

        weak_ptr<MyString> wp(p1); // 直接初始化
        weak_ptr<MyString> wp2 = p1; // 赋值初始化
        cout << p1 << ", " << *p1 << ", " << p1.use_count() << ", " << wp.use_count() << endl; // 0x600001b45218, p1, 1, 1

        // 我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。
        // 如果存在,lock返回一个指向共享对象的shared_ptr。
        auto p2 = wp.lock();
        cout << (p1 == p2) << endl; // true
    }
    // p1~析构函数
    cout << "weak_ptr<MyString> count: " << pOuter.use_count() << endl; // weak_ptr<MyString> count: 0

删除器

不是所有的类都有析构函数的定义的。特别是那些为C和C++两种语言设计的类,通常都要求用户显式地释放所使用的任何资源。
所以在使用智能指针时要释放这种资源,需要自定义删除器。

// test.h
class Connection {
public:
    explicit Connection(string name) : _name(std::move(name)) {
    }

    ~Connection() {
        cout << _name << "~析构函数" << endl;
    }

    string get_name() const {
        return _name;
    }

private:
    string _name;
};

inline void close(Connection *connection) {
    cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;
}
// test.cpp

void Deleter(Connection *connection) {
    close(connection);
    delete connection;
}

void testDeleter() {
    {
        // 直接使用智能指针无法调用close函数
        shared_ptr<Connection> pts = make_shared<Connection>("111");
        unique_ptr<Connection> ptu(new Connection("222"));
    }
    // 222~析构函数
    // 111~析构函数

    {
        shared_ptr<Connection> sp(new Connection("shared_ptr"), Deleter);
        unique_ptr<Connection, decltype(Deleter) *> up(new Connection("unique_ptr"), Deleter);
    }
//    关闭unique_ptr管理的连接中...
//    unique_ptr~析构函数
//    关闭shared_ptr管理的连接中...
//    shared_ptr~析构函数

    auto DeleterLambda = [](Connection *connection) {
        close(connection);
        delete connection;
    };

    shared_ptr<Connection> sp2(new Connection("shared_ptr"), DeleterLambda);
    unique_ptr<Connection, decltype(DeleterLambda)> up2(new Connection("unique_ptr"), DeleterLambda);
//    关闭unique_ptr管理的连接中...
//    unique_ptr~析构函数
//    关闭shared_ptr管理的连接中...
//    shared_ptr~析构函数
}

参考:
现代C++编程实战02-自己动手,实现C++的智能指针
C++11 中智能指针的原理、使用、实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baiiu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值