智能指针

std::unique_ptr

代码如下:

#include <iostream>
#include <memory>


int main() {
    {
        // 创建 unique_ptr

        auto myIntSmartPtr {std::make_unique<int>(10)};

        std::cout << *myIntSmartPtr << std::endl;
        *myIntSmartPtr = 33;
        std::cout << *myIntSmartPtr << std::endl;
    }

    class Simple {
    public:
        Simple() {std::cout << "Simple() called!" << std::endl;}
        ~Simple() {std::cout << "~Simple() called!" << std::endl;}
        void go() {std::cout << "go() called!" << std::endl;}
    };

    {
        // 创建 unique_ptr

        // 方法1
        auto mySimpleSmartPtr {std::make_unique<Simple>()};
        mySimpleSmartPtr->go();

        // 方法2
        std::unique_ptr<Simple> mySimpleSmartPtr2 {new Simple{}};
        mySimpleSmartPtr2->go();
    }

    {
        // 从 unique_ptr 获取原始指针
        // 使用成员函数 get() 访问底层指针

        auto processData{[](Simple* simple){
            simple->go();
        }};

        auto mySimpleSmartPtr {std::make_unique<Simple>()};
        processData(mySimpleSmartPtr.get());
    }

    {
        // 释放 unique_ptr 底层指针
        // 成员函数 reset(),两种用途:
        // 释放底层资源,将底层指针设置为nullptr
        // 释放底层资源,将底层指针指向新的对象实例
        auto mySimpleSmartPtr {std::make_unique<Simple>()};
        
        mySimpleSmartPtr.reset();
        if (mySimpleSmartPtr == nullptr) {std::cout << "mySimpleSmartPtr == nullptr" << std::endl;}

        mySimpleSmartPtr.reset(new Simple{});
        mySimpleSmartPtr->go();
    }

    {
        // 断开 unique_ptr 与 底层指针 的连接
        // 使用成员函数 release()
        // unique_ptr 失去了对资源的所有权,所以就不再管理资源,所以需要手动释放资源
        // unique_ptr 此时被设置为 nullptr
        auto mySimpleSmartPtr {std::make_unique<Simple>()};
        Simple* simple {mySimpleSmartPtr.release()};

        // 需要手动释放资源了
        delete simple;
        simple = nullptr;
    }

    {
        // 移动 unique_ptr 的所有权
        // 将 unique_ptr 智能指针作为类的数据成员

        class Foo {
        public:
            Foo(std::unique_ptr<int> data) : m_data {std::move(data)} {}
        private:
            std::unique_ptr<int> m_data;
        };

        auto myIntSmartPtr {std::make_unique<int>(42)};
        Foo f {std::move(myIntSmartPtr)};
    }

    {
        // 使用 unique_ptr 管理动态分配的 C 风格数组
        auto myVariableSizedArray {std::make_unique<int[]>(10)};  // 含有 10 个整型元素的C风格数组
        // myVariableSizedArray 的类型是 std::unique_ptr<int[]>

        myVariableSizedArray[1] = 123;
    }

    {
        // 自定义删除器
        // 默认 unique_ptr 使用标准 new 和 delete 运算符来分配和释放内存
        // 可以修改默认行为,使用自定义的分配和释放函数
        auto my_alloc {[](int value)->int* {
            std::cout << "my_alloc() called!" << std::endl;
            return new int {value};
        }};

        auto my_free {[](int* p){
            std::cout << "my_free() called!" << std::endl;
            delete p;
        }};

        std::unique_ptr<int, decltype(my_free)> myIntSmartPtr {my_alloc(10), my_free};
    
    }

    return 0;
}

std::shared_ptr

#include <iostream>
#include <memory>


int main() {
    class Simple {
    public:
        Simple() {std::cout << "Simple() called!" << std::endl;}
        ~Simple() {std::cout << "~Simple() called!" << std::endl;}
        void go() {std::cout << "go() called!" << std::endl;}
    };

    {
        // 创建 shared_ptr
        auto mySimpleSmartPtr {std::make_shared<Simple>()};
    }

    {
        // 自定义删除器
        // 和 unique_ptr 一样,默认 shared_ptr 使用标准 new 和 delete 运算符来分配和释放内存
        // 相比 unique_ptr 更方便一点,不需要将自定义删除器的类型指定为模板类型参数

        auto my_alloc {[](int value)->int* {
            std::cout << "my_alloc() called!" << std::endl;
            return new int {value};
        }};

        auto my_free {[](int* p){
            std::cout << "my_free() called!" << std::endl;
            delete p;
        }};

        std::shared_ptr<int> myIntSmartPtr {my_alloc(10), my_free};
    }

    {
        // 用 shared_ptr 储存旧式 C 风格文件指针(不需要再手动关闭文件)

        auto close {[](FILE* filePtr){
            if (filePtr == nullptr) {return;}
            fclose(filePtr);
            std::cout << "文件已关闭" << std::endl;
        }};

        FILE* f {fopen("data.txt", "w")};
        std::shared_ptr<FILE> filePtr {f, close};

        if (filePtr == nullptr) {std::cerr << "打开文件失败" << std::endl;}
        else {std::cout << "文件已打开" << std::endl;}
    }

    {
        // 别名(aliasing),
        // 让一个 shared_ptr 共享另一个 shared_ptr 底层的指针(记为 A)的所有权,
        // 但是这个 shared_ptr 却可以指向另一个对象,这个对象与 A 指向的对象不是同一个。

        class Foo {
        public:
            Foo(int value) : m_data {value} {}
            int m_data;
        };

        auto foo {std::make_shared<Foo>(42)};
        auto aliasing {std::shared_ptr<int> {foo, &foo->m_data}};
        // 智能指针 aliasing 与 智能指针 foo 共享对底层指针(记为A,官方名称 owned pointer)的所有权,
        // 但智能指针 aliasing 却指向另一个对象(官方名称 stored pointer),在这里是 A 指向的对象的数据成员 m_data。
        // 当两个 shared_ptr (foo 和 aliasing)都被销毁时,`Foo`对象才会被销毁。
        // "owned pointer"用于引用计数,"stored pointer"用于对智能指针解引用、对智能指针调用`get()`时返回

        std::cout << foo->m_data << std::endl;  // foo 指向一个 Foo 对象
        std::cout << *aliasing << std::endl;  // aliasing 指向 foo 指向的 Foo 对象的数据成员 m_data
    }

    return 0;
}

std::weak_ptr

是什么?为什么?怎么用?

#include <iostream>
#include <memory>


int main() {

    {
        // 演示 shared_ptr 无法正确处理的一种情况,引入 weak_ptr

        class Simple {
        public:
            Simple() {std::cout << "Simple() called!" << std::endl;}
            ~Simple() {std::cout << "~Simple() called!" << std::endl;}
            std::shared_ptr<Simple> m_ptr;
        };

        auto mySimpleSmartPtr1 {std::make_shared<Simple>()};
        auto mySimpleSmartPtr2 {std::make_shared<Simple>()};

        mySimpleSmartPtr1->m_ptr = mySimpleSmartPtr2;
        mySimpleSmartPtr2->m_ptr = mySimpleSmartPtr1;

        // 出现的情况是,两个对象能够正常构造,却没有正常析构。
        // mySimpleSmartPtr2 被 mySimpleSmartPtr1 使用,所以 mySimpleSmartPtr2 指向的对象的引用计数为 1
        // mySimpleSmartPtr1 被 mySimpleSmartPtr2 使用,所以 mySimpleSmartPtr1 指向的对象的引用计数为 1
        // 两者互相被对方使用,两者指向的对象的引用计数都不会降为0,对方不析构,自己就不能析构,
        // 所以两者指向的对象的析构函数都不会被调用
    }

    {
        // 解决上一个程序中 shared_ptr 无法正确处理的一种情况
        // 将类 Simple 的数据成员 m_ptr 的类型由 std::shared_ptr<Simple> 改为 std::weak_ptr<Simple> 可解决
        class Simple {
        public:
            Simple() {std::cout << "Simple() called!" << std::endl;}
            ~Simple() {std::cout << "~Simple() called!" << std::endl;}
            std::weak_ptr<Simple> m_ptr;  // !!! 这里变动,其他未变
        };

        auto mySimpleSmartPtr1 {std::make_shared<Simple>()};
        auto mySimpleSmartPtr2 {std::make_shared<Simple>()};

        mySimpleSmartPtr1->m_ptr = mySimpleSmartPtr2;
        mySimpleSmartPtr2->m_ptr = mySimpleSmartPtr1;

        // 为什么能解决?
        // weak_ptr 可以包含一个由 shared_ptr 管理的资源的引用,
        // weak_ptr 可以访问这个资源,但是不拥有这个资源的所有权,不会阻止 shared_ptr 释放该资源
    }

    class Simple {
    public:
        Simple() {std::cout << "Simple() called!" << std::endl;}
        ~Simple() {std::cout << "~Simple() called!" << std::endl;}
        void go() {std::cout << "go() called!" << std::endl;}
    };

    {
        // 创建 weak_ptr
        // weak_ptr 的构造函数需要一个 shared_ptr 或一个 weak_ptr 作为参数

        auto sharedSimple {std::make_shared<Simple>()};
        std::weak_ptr<Simple> weakSimple {sharedSimple};  // 这里从一个 shared_ptr 创建 weak_ptr
    }

    {
        // 使用 weak_ptr

        auto useResource{[](std::weak_ptr<Simple>& weakSimple){
            // 成员函数 lock() 返回 shared_ptr
            // 若 shared_ptr 管理的资源已被释放,则返回的 shared_ptr 底层为 nullptr
            auto resource {weakSimple.lock()};
            if (resource) {std::cout << "资源当前仍然存活。" << std::endl;}
            else {std::cout << "资源已经被释放" << std::endl;}
        }};

        auto sharedSimple {std::make_shared<Simple>()};
        std::weak_ptr<Simple> weakSimple {sharedSimple};  // 这里从一个 shared_ptr 创建 weak_ptr

        // 传入 weak_ptr,与该 weak_ptr 关联的 shared_ptr 管理的资源未释放
        useResource(weakSimple);

        // 传入 weak_ptr,与该 weak_ptr 关联的 shared_ptr 管理的资源已被释放
        sharedSimple.reset();
        useResource(weakSimple);

    }

    return 0;
}

日常使用

#include <iostream>
#include <memory>


int main() {

    {
        // 从函数返回智能指针
        // 直接按值返回即可,因为有 copy elision,可以理解为编译器有优化,很高效

        class Simple {
        public:
            Simple() { std::cout << "Simple() called!" << std::endl; }
            ~Simple() { std::cout << "~Simple() called!" << std::endl; }
            void go() { std::cout << "go() called!" << std::endl; }
        };

        auto create{ []() -> std::unique_ptr<Simple> {
            auto ptr {std::make_unique<Simple>()};
            // Do something with ptr...
            return ptr;
        } };

        // 方式1
        std::unique_ptr<Simple> mySmartPtr1{ create() };

        // 方式2,使用auto关键字
        auto mySmartPtr2{ create() };
    }

    {
        // 让一个对象可以返回一个指向自身的 shared_ptr 或 weak_ptr
        // 需要让类继承自 enable_shared_from_this<>

        class Foo : public std::enable_shared_from_this<Foo> {
        public:
            Foo() { std::cout << "Foo() called!" << std::endl; }
            ~Foo() { std::cout << "~Foo() called!" << std::endl; }
            std::shared_ptr<Foo> getPointer() {
                return shared_from_this();
            }
        };

        // 必须要先创建一个 shared_ptr,才能调用成员函数 shared_from_this()
        // (不一定通过这个 shared_ptr 来调用 shared_from_this(),但要有至少这么一个shared_ptr。)
        auto ptr1{ std::make_shared<Foo>() };
        auto ptr2{ ptr1->getPointer() };

        // 始终可以在一个对象上调用成员函数 weak_from_this()
        // 但如果在这个对象上还没有创建过一个 shared_ptr,返回的 weak_ptr 可能是空的

        // 函数:判断一个 weak_ptr 是不是空的
        auto isWeakPtrEmpty{ [](std::weak_ptr<Foo> const& weak_ptr) {
            if (weak_ptr.expired()) { std::cout << "weak_ptr 是空的" << std::endl; }
            else { std::cout << "weak_ptr 不是空的" << std::endl; }
        } };

        Foo* fooPtr{ new Foo{} };
        // 此时在 fooPtr 指向的对象上没有创建 shared_ptr,所以此时 weak_ptr 是空的
        auto ptrWeak{ fooPtr->weak_from_this() };
        isWeakPtrEmpty(ptrWeak);

        // 创建 shared_ptr
        auto ptrShared{ std::shared_ptr<Foo>{fooPtr} };
        // 此时 weak_ptr 不是空的
        ptrWeak = ptrShared->weak_from_this();
        isWeakPtrEmpty(ptrWeak);

    }

    {
        // 智能指针 与 C风格函数 一起使用
        // C 风格函数,喜欢通过返回值返回一个整数,以此判断函数是否正确运行;
        // 而真正要得到的数据,通过参数来返回(这样的参数叫做“输出参数”)

        using errorcode = int;
        auto my_alloc{ [](int value, int** data) -> errorcode {
            *data = new int {value};
            return 0;
        } };
        auto my_free{ [](int* data) {
            delete data;
            return 0;
        } };

        // 怎样用智能指针管理 通过参数返回的 动态内存?

        // C++23 前:
        std::unique_ptr<int, decltype(my_free)> myIntSmartPtr{ nullptr, my_free };
        int* data{ nullptr };
        my_alloc(42, &data);
        myIntSmartPtr.reset(data);

        std::cout << *myIntSmartPtr << std::endl;


        // 能否改成下面这样?:
        int* data1{ nullptr };
        my_alloc(42, &data1);
        std::unique_ptr<int, decltype(my_free)> myIntSmartPtr2{ data1, my_free };
        // 看下一个代码片段(下一个大花括号),认为两种声明 unique_ptr 的方式是等价的,都有效。
        // 我想,作者这么写,可能是为了和下面他要介绍的C++23的书写方式的语句顺序保持一致。

        std::cout << *myIntSmartPtr2 << std::endl;

        // C++23:
        std::unique_ptr<int, decltype(my_free)> myIntSmartPtr3{ nullptr, my_free };
        my_alloc(42, std::inout_ptr(myIntSmartPtr3));

        std::cout << *myIntSmartPtr2 << std::endl;
    }

    {
        // 实验 上一个代码片段,两种声明 unique_ptr 的方式是否等价
        class Simple {
        public:
            Simple() { std::cout << "Simple() called!" << std::endl; }
            ~Simple() { std::cout << "~Simple() called!" << std::endl; }
            void go() { std::cout << "go() called!" << std::endl; }
        };

        using errorcode = int;
        auto my_alloc{ [](Simple** data) -> errorcode {
            *data = new Simple {};
            return 0;
        } };
        auto my_free{ [](Simple* data) {
            delete data;
            return 0;
        } };

        // C++23 前:
        std::unique_ptr<Simple, decltype(my_free)> mySimpleSmartPtr{ nullptr, my_free };
        Simple* data{ nullptr };
        my_alloc(&data);
        mySimpleSmartPtr.reset(data);

        // 能否改成这样:
        Simple* data1{ nullptr };
        my_alloc(&data1);
        std::unique_ptr<Simple, decltype(my_free)> myIntSmartPtr2{ data1, my_free };

        // 经测试,用这两种方式创建的 unique_ptr 都能正常释放内存
        // 应该可以说明,二者是等价的
    }

    return 0;
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注