IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Item 18: Use std::unique_ptr for exclusive-ownership resource management. -> 正文阅读

[C++知识库]Item 18: Use std::unique_ptr for exclusive-ownership resource management.

Item 18: Use std::unique_ptr for exclusive-ownership resource management.

Effective Modern C++ Item 18 的学习和解读。

原始指针非常灵活,但是使用陷阱多,容易出错,智能指针则更容易使用。本文介绍的智能指针是 std::unique_ptr。

独占所有权

std::unique_ptr 表现出独占所有权的语义。一个非空的 std::unique_ptr 总是对它指向的资源拥有独占所有权,它不共享它指向的资源给其他指针。因此,无法通过值传递 std::unique_ptr 给函数,也不允许复制 std::unique_ptr。看下面的例子,注意 std::make_unique 在 C++14 才开始支持,从报错信息也可以看到拷贝构造函数是 delete 的。

#include<iostream>
#include<memory>

int main() 
{
    std::unique_ptr<int> pInt(new int(5));
    // std::unique_ptr<int> pInt = std::make_unique<int>(5);  // C++14 才支持
    std::unique_ptr<int> pInt1(pInt);    // 报错
}
// 报错信息:
main.cpp: In function 'int main()':
main.cpp:8:36: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
    8 |     std::unique_ptr<int> pInt1(pInt);    // 报错
      |                                    ^
In file included from /usr/local/include/c++/11.2.0/memory:76,
                 from main.cpp:2:
/usr/local/include/c++/11.2.0/bits/unique_ptr.h:468:7: note: declared here
  468 |       unique_ptr(const unique_ptr&) = delete;
      |    

std::unique_ptr 是 move-only 类型,可以 move 它的控制权,原 std::unique_ptr 则变为空指针。看下面的例子:

#include<iostream>
#include<memory>

int main() 
{
    std::unique_ptr<int> pInt(new int(5));
    std::unique_ptr<int> pInt2 = std::move(pInt);    // 转移所有权
    // std::cout << *pInt << std::endl; // Segmentation fault (core dumped) ./a.out
    std::cout << *pInt2 << std::endl;
    std::unique_ptr<int> pInt3(std::move(pInt2));
}

std::unique_ptr 虽然不支持复制,但有个例外:可以从函数返回一个 std::unique_ptr。

#include<iostream>
#include<memory>

std::unique_ptr<int> func(int x)
{
    std::unique_ptr<int> pInt(new int(x));
    return pInt;
}

int main() {
    int x = 5;
    std::unique_ptr<int> p = func(x);
    std::cout << *p << std::endl;
}

占用内存的大小

相较于其他智能指针,std::unique_ptr 有一个优势:在不自定义删除器的情况下,std::unique_ptr 的内存占用几乎和原始指针一致。

#include<iostream>
#include<memory>

int main() {
  int *p = new int(5);
  std::unique_ptr<int> pu(new int(6));
  std::cout << sizeof(p) << ":" << sizeof(pu) << std::endl;
  return 0;
}
// 输出:8:8 

std::unique_ptr 内部几乎不用维护其他信息(std::shared_ptr 需要维护引用计数),当它离开作用域,是通过 delete 删除指向的资源。但是,如果自定义了删除器,则会增加内存占用。

#include<iostream>
#include<memory>

int main() {
  int c = 2;
  int d = 3;
  // 带参数捕捉的lambda表达式,会导致unique_ptr占用内存变大
  auto delint = [&](int *p) {
    std::cout << "c = " << c << std::endl;
    std::cout << "d = " << d << std::endl;
    std::cout << "deleter" << std::endl;
    delete p;
  };
  std::unique_ptr<int, decltype(delint)> p(new int(10), delint);
  std::cout << sizeof(p) << std::endl;
  return 0;
}
// 输出:
24
c = 2
d = 3
deleter

一个典型应用

std::unique_ptr 的一个典型应用是作为一个工厂函数的返回类型(指向类层次中的对象)。这里直接使用这里的代码作为例子:

#include<iostream>
#include<memory>

using namespace std;

/*!
 * \brief The Investment class 基类
 */
class Investment
{
public:
    virtual ~Investment() = default;

public:
    virtual void doWork() = 0;
};

/*!
 * \brief The Stock class 派生类
 */
class Stock : public Investment
{
public:
    virtual void doWork() override {
        cout << "Stock doWork....\n";
    }
};

/*!
 * \brief The Stock class 派生类
 */
class Bond : public Investment
{
public:
    virtual void doWork() override {
        cout << "Bond doWork....\n";
    }
};

enum class InvestType {
    INVEST_TYPE_STOCK,
    INVEST_TYPE_BOND,
};

auto makeInvestment(InvestType type)
{
    // 自定义析构器, 这里以lambda表达式的形式给出
    auto delInvmt = [](Investment *pInvestment) {
        // TODO 自定义析构时想干的事
        cout << "delInvmt called...." << endl;
        delete pInvestment;
    };

    // 待返回的指针, 初始化为空指针,并指定自定义析构器
    // decltype(delInvmt) 用于获取自定义析构器的类型
    unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);

    // 注意这里用reset来指定pInv获取从new产生的对象的所有权, 不能用=赋值
    switch (type)
    {
    case InvestType::INVEST_TYPE_STOCK:
        //pInv = new Stock; // error!! c++11禁止从裸指针到智能指针的隐式转换
        pInv.reset(new Stock);
        break;
    case InvestType::INVEST_TYPE_BOND:
        pInv.reset(new Bond);
        break;
    }

    // 返回智能指针
    return pInv;
}

void test()
{
    // 测试工厂函数
    {
        // pInv出作用域后会自己析构
        auto pInv = makeInvestment(InvestType::INVEST_TYPE_STOCK);
        if (pInv)
        {
            pInv->doWork();
        }
    }

    cout << "----------------\n";

    // 测试move效果
    {
        auto pInv = makeInvestment(InvestType::INVEST_TYPE_BOND);
        auto pInv2 = move(pInv);
        cout << "after move pInv to pInv2 \n";
        if (!pInv)
        {
            cout << "pInv is empty \n";
        }
        if (pInv2)
        {
            cout << "pInv2 is valid \n";
            pInv2->doWork();
        }
    }

    cout << "----------------\n";

    // 测试unique_ptr向shared_ptr转换
    {
        shared_ptr<Investment> pInv = makeInvestment(InvestType::INVEST_TYPE_BOND);
        pInv->doWork();
    }
}

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

// 输出:
Stock doWork....
delInvmt called....
----------------
after move pInv to pInv2 
pInv is empty 
pInv2 is valid 
Bond doWork....
delInvmt called....
----------------
Bond doWork....
delInvmt called....

杂项

std::unique_ptr 通过 std::unique_ptr<T[]> 形式支持指向数组,并通过 delete [] 释放资源。

#include<iostream>
#include<memory>

int main() 
{
    std::unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
    p[0] = 0;   // 重载了operator[]
}

std::unique_ptr 可以直接隐式转换为 std::shared_ptr。

std::shared_ptr<Investment> sp =   // converts std::unique_ptr
  makeInvestment( arguments );     // to std::shared_ptr

总结一下:

  • std::unique_ptr 是一个小的、快的、move-only 的智能指针,它能用来管理资源,并且独占资源的所有权。
  • 默认情况下,std::unique_ptr 资源的销毁是用 delete 进行的,但也可以用户自定义 deleter。用带状态的 deleter 和函数指针作为 deleter 会增加 std::unique_ptr 对象的大小。
  • 很容易将 std::unique_ptr 转换为 std::shared_ptr。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:13:21  更:2022-03-15 22:16:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:19:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码