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
#include

int main() 
{
    std::unique_ptr<int> pInt(new int(5));
    // std::unique_ptr pInt = std::make_unique(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]'
    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
#include

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
#include

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
#include

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
#include

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
#include

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 形式支持指向数组,并通过 delete [] 释放资源。

#include
#include

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。

你可能感兴趣的:(Effective,Modern,C++,C++,c++)