C++11——智能指针

unique_ptr

unique_ptr 用来管理具备专属所有权的资源,它通过指针持有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象。下面两种情况下,所管理的资源被释放:

  1. unique_ptr 对象被销毁(离开作用域,调用析构函数)
  2. 通过 operator= 或 reset() 赋值另一指针给管理它的 unique_ptr 对象
  • 对象的释放通过调用 get_deleter()(ptr) 实现,默认删除器使用 delete 运算符,也可以自定义删除器释放对象。

std::unique_ptr 有两个版本:

  1. 管理单个对象(new 分配)
template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;
  1. 管理动态分配的对象数组(new[] 分配)
template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;

示例代码:

#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <stdexcept>
 
// 用于下面运行时多态演示的辅助类
struct B
{
    virtual ~B() = default;
 
    virtual void bar() { std::cout << "B::bar\n"; }
};
 
struct D : B
{
    D() { std::cout << "D::D\n"; }
    ~D() { std::cout << "D::~D\n"; }
 
    void bar() override { std::cout << "D::bar\n"; }
};
 
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
 
// 用于下面自定义删除器演示的辅助函数
void close_file(std::FILE* fp)
{
    std::fclose(fp);
}
 
// 基于 unique_ptr 的链表演示
struct List
{
    struct Node
    {
        int data;
        std::unique_ptr<Node> next;
    };
 
    std::unique_ptr<Node> head;
 
    ~List()
    {
        // 循环按顺序销毁各列表节点,默认析构函数将会递归调用其 `next` 指针的析构函数,
        // 这在足够大的链表上可能造成栈溢出。
        while (head)
        {
            auto next = std::move(head->next);
            head = std::move(next);
        }
    }
 
    void push(int data)
    {
        head = std::unique_ptr<Node>(new Node{data, std::move(head)});
    }
};
 
int main()
{
    std::cout << "1) 独占所有权语义演示\n";
    {
        // 创建一个(独占)资源
        std::unique_ptr<D> p = std::make_unique<D>();
 
        // 转移所有权给 `pass_through`,而它再通过返回值将所有权转移回来
        std::unique_ptr<D> q = pass_through(std::move(p));
 
        // p 现在是已被移动的“空”状态,等于 nullptr
        assert(!p);
    }
 
    std::cout << "\n" "2) 运行时多态演示\n";
    {
        // 创建派生类资源并通过基类指向它
        std::unique_ptr<B> p = std::make_unique<D>();
 
        // 动态派发如期工作
        p->bar();
    }
 
    std::cout << "\n" "3) 自定义删除器演示\n";
    std::ofstream("demo.txt") << 'x'; // 准备要读取的文件
    {
        using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
        unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
        if (fp)
            std::cout << char(std::fgetc(fp.get())) << '\n';
    } // `close_file()` 于此调用(若 `fp` 为空)
 
    std::cout << "\n" "4) 自定义 lambda 表达式删除器和异常安全性演示\n";
    try
    {
        std::unique_ptr<D, void(*)(D*)> p(new D, [](D* ptr)
        {
            std::cout << "由自定义删除器销毁...\n";
            delete ptr;
        });
 
        throw std::runtime_error(""); // `p` 若为普通指针则此处将泄漏
    }
    catch (const std::exception&)
    {
        std::cout << "捕获到异常\n";
    }
 
    std::cout << "\n" "5) 数组形式的 unique_ptr 演示\n";
    {
        std::unique_ptr<D[]> p(new D[3]);
    } // `D::~D()` 被调用 3 次
 
    std::cout << "\n" "6) 链表演示\n";
    {
        List wall;
        const int enough{1'000'000};
        for (int beer = 0; beer != enough; ++beer)
            wall.push(beer);
 
        std::cout.imbue(std::locale("en_US.UTF-8"));
        std::cout << "墙上有 " << enough << " 瓶啤酒...\n";
    } // 销毁所有啤酒
}

shared_ptr

shared_ptr 管理具备共享所有权的资源,多个 shared_ptr 对象可持有同一对象。下列情况之一出现时销毁对象并解分配其内存():

  1. 最后剩下的持有对象的 shared_ptr 被销毁;
  2. 最后剩下的持有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

实现机制:引用计数

std::shared_ptr 中有两个指针:

  • 所存储的指针(get()) 所返回的指针)
  • 指向控制块 的指针

控制块是一个动态分配的对象,其中包含:

  • 指向被管理对象的指针或被管理对象本身
  • 删除器(类型擦除)
  • 分配器(类型擦除)
  • 持有被管理对象的 shared_ptr 的数量
  • 涉及被管理对象的 weak_ptr 的数量

shared_ptr 的构造函数会将控制块中的引用计数加1 ,析构函数会将引用计数减1,如果该计数器减至零,控制块就会调用被管理对象的析构函数。但控制块本身直到 std::weak_ptr 计数器同样归零时会解分配其自身。

使用 make_shared 的优势:

  • 调用 std::make_shared 创建 shared_ptr 时,以单次分配创建控制块和被管理对象。被管理对象在控制块的数据成员中原位构造。
  • 使用 new 初始化 std::shared_ptr 时:
  1. 首先,使用 new 在堆上为对象实例分配内存并完成初始化。
  2. 然后,使用已分配的对象指针来构造 std::shared_ptr 实例。

示例代码:

#include <chrono>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
 
using namespace std::chrono_literals;
 
struct Base
{
    Base() { std::cout << "Base::Base()\n"; }
 
    // 注意:此处非虚析构函数 OK
    ~Base() { std::cout << "Base::~Base()\n"; }
};
 
struct Derived : public Base
{
    Derived() { std::cout << "Derived::Derived()\n"; }
 
    ~Derived() { std::cout << "Derived::~Derived()\n"; }
};
 
void print(auto rem, std::shared_ptr<Base> const& sp)
{
    std::cout << rem << "\n\tget() = " << sp.get()
              << ", use_count() = " << sp.use_count() << '\n';
}
 
void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(987ms);
    std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        print("线程中的局部指针:", lp);
    }
}
 
int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();
 
    print("创建共享的 Derived (为 Base 指针)", p);
 
    std::thread t1{thr, p}, t2{thr, p}, t3{thr, p};
    p.reset(); // 从 main 释放所有权
 
    print("在 3 个线程间共享所有权并从 main 释放所有权:", p);
 
    t1.join();
    t2.join();
    t3.join();
 
    std::cout << "线程全部已完成,最后一个删除了 Derived。\n";
}

weak_ptr

  • std::weak_ptr 是一种智能指针,它持有对被 std::shared_ptr 管理的对象的非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。

  • std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。

  • std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。可通过令环中的指针之一为弱指针来避免这种情况。

示例代码:

#include <iostream>
#include <memory>
 
std::weak_ptr<int> gw;
 
void observe()
{
    std::cout << "gw.use_count() == " << gw.use_count() << "; ";
    // 使用之前必须制作一个 shared_ptr 副本
    if (std::shared_ptr<int> spt = gw.lock())
        std::cout << "*spt == " << *spt << '\n';
    else
        std::cout << "gw 已过期\n";
}
 
int main()
{
    {
        auto sp = std::make_shared<int>(42);
	gw = sp;
 
	observe();
    }
 
    observe();
}

一些关于智能指针的面试问题

  1. 什么是智能指针?解释其作用和重要性。
  • 智能指针是C++中一种特殊的指针类型,它以类的形式封装了原始指针,用来自动管理动态分配的内存,从而避免内存泄漏、悬挂指针、重复释放等问题。智能指针通过自动执行析构函数来释放内存,确保即使在异常情况下也能正确清理资源,这极大地提高了代码的安全性和可靠性。
  • 要点:智能指针是什么
  1. 为什么在C++中使用智能指针而不是原始指针?
  2. 列举并解释C++标准库中提供的几种智能指针类型
  3. 什么时候应该使用unique_ptr?它有何特点?
  4. 解释unique_ptr的转移语义(move semantics)。
  5. 如何正确地使用std::move与unique_ptr?
  6. shared_ptr的工作原理是什么?特别是它的引用计数机制。
  7. shared_ptr与unique_ptr的主要区别是什么?
  8. 解释循环引用问题,并说明如何使用weak_ptr来解决这个问题。
  9. 什么是weak_ptr?它存在的目的是什么?
  10. 如何从weak_ptr获得一个shared_ptr?为什么需要这样的转换?
  11. shared_ptr是不是线程安全?
  12. 为什么推荐用mak_eshared创建指针?
  13. weak_ptr如何检测指针是否被销毁?
  14. 如何将unique_ptr转换成shared_ptr类型?
  15. 捕获shared_ptr时如果不想延长对象生命周期怎么做?
  16. unique_ptr能否被另一个unique_ptr拷贝呢?
  • 38
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值