unique_ptr, effective modern c++笔记

来源:互联网 发布:php检查类是否存在 编辑:程序博客网 时间:2024/06/04 01:11

为什么要用智能指针呢?

因为裸指针存在很多问题,主要是下面这些:

  1. 难以区分指向的是单个对象还是一个数组;
  2. 使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;
  3. 在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;
  4. 即便已经确定了销毁指针的方法,由于1的原因,仍然无法确定到底是用delete(销毁单个对象)还是delete;
  5. 假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为;
  6. 理论上没有方法来分辨一个指针是否处于悬挂状态;

智能指针

智能指针主要是下面几个,c++11之后就引入到标准库成为语言新特性了(之前是在boost库中)

  • std::auto_ptr, std::unique_ptr
  • std::shared_ptr, std::weak_ptr

上述四个智能指针类型中,auto_ptr是c++ 98遗留的关键字,已经不建议使用,auto_ptr的功能都可以由unique_ptr更加高效的做到;本文主要先讲一讲unique_ptr;

unique_ptr在要表达“专属所有权”的语义时使用,即unique_ptr指针永远“拥有”其指向的对象,所以unique_ptr是一个move-only类型,一个unique_ptr指针是无法被复制的,只能将“所有权”在两个unique_ptr指针之间转移,转移完成后源unique_ptr将被设为null;

定义一个unique_ptr并指向一块动态内存:

std::unique_ptr<int> pInt(nullptr);pInt.reset(new int(1));std::cout << *pInt << "\n"; //解引用操作与裸指针相同,打印的结果为1

unique_ptr一个常用的场景是在使用工厂模式的时候,在工厂方法中返回一个unique_ptr类型的指针,例如

#pragma once//investment.h#include <assert.h>#include <memory>#include <iostream>#include <functional>//类定义class Investment {public:    virtual ~Investment() {        std::cout << "investment destoryed\n";    }};void makeLogEntry(Investment* pInv) {    std::cout << "deleting investment on " << pInv << "\n";}class Stock : public Investment {public:    Stock() {        std::cout << "make an invesetment on stock\n";    }    virtual ~Stock() {        std::cout << "a stock investment destoryed,";    }};class Bond : public Investment {public:    Bond() {        std::cout << "make an investmentt on bond\n";    }    virtual ~Bond() {        std::cout << "a bond investment destroyed,";    }};class RealEstate : public Investment {public:    RealEstate() {        std::cout << "make an investmentt on RealEstate\n";    }    virtual ~RealEstate() {        std::cout << "a RealEstatend investment destroyed,";    }};void deleteAndLog(Investment* pInv) {    makeLogEntry(pInv);    delete pInv;}template<typename T, typename... Ts>static auto makeInvestment(Ts&&... params) {    auto delInvmt = [](Investment* pInv)    {        makeLogEntry(pInv);        delete pInv;    };    typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;    std::cout << sizeof(InvestmentPtr) << "\n";    InvestmentPtr pInv(nullptr, delInvmt);    pInv.reset(new T(std::forward<Ts>(params)...));//不能直接将裸指针赋值给一个unique_ptr,要使用reset    return pInv;}

客户端的调用方法

auto pInvestment = InvestmentMaker::makeInvestment<Stock>();

unique_ptr默认的销毁方式是通过对unique_ptr中的裸指针进行delete操作,但是也可以在声明的时候指定销毁函数,在上面的代码中,通过lambda表达式置顶了一个打印日志函数,要在销毁指针的时候会打印日志,

template<typename T, typename... Ts>    static auto makeInvestment(Ts&&... params) {        auto delInvmt = [](Investment* pInv)                        {                            makeLogEntry(pInv);                            delete pInv;                        };        typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;        InvestmentPtr pInv(nullptr, delInvmt);        pInv.reset(new T(std::forward<Ts>(params)...));//不能直接将裸指针赋值给一个unique_ptr,要使用reset        return pInv;    }

这里使用了c++ 14支持auto函数返回类型推导的特性,如果是c++11的话就需要把lambda表达式写到makeInvestment方法外面了。

在使用默认delete操作的时候,unique_ptr的内存size与裸指针一致,而使用自定义销毁方式的时候,unique_ptr的size取决于自定义销毁的方式:
* 在使用函数指针的时候,unique_ptr增加一个或两个字长;
* 使用函数对象的时候,unique_ptr的size取决于函数对象中的语句数量;
* 使用不带capture的lambda表达式时,不增加size

void deleteAndLog(Investment* pInv) {    makeLogEntry(pInv);    delete pInv;}template<typename T, typename... Ts>static auto makeInvestment(Ts&&... params) {    auto delInvmt = [](Investment* pInv)    {        makeLogEntry(pInv);        delete pInv;    };    typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;    std::cout << sizeof(InvestmentPtr) << "\n"; //lambda表达式不增加size, size为4    typedef std::unique_ptr<Investment, decltype(&deleteAndLog)> FunPtrInvestmentPtr;    std::cout << sizeof(FunPtrInvestmentPtr) << "\n"; //函数指针增加1一个字长, size为8    std::function<void(Investment*)> funcionObj = deleteAndLog;    typedef std::unique_ptr<Investment, decltype(funcionObj)> FunObjInvestmentPtr;    std::cout << sizeof(FunObjInvestmentPtr) << "\n"; //函数对象增加大小取决于函数体的语句数量,这里的size为48    //...}

unique_ptr是c++11之后用来表示专属所有权的一种智能指针,除了上面讲到的这些特性之外,另外还有一个非常方便的特性就是可以无缝地转换成shared_ptr,以上面的代码为例子,在调用工厂方法的时候,可以直接赋值给一个shared_ptr指针

std::shared_ptr<Investment> pInvestment = makeInvestment<Stock>();

shared_ptr将在下一篇博客中来讲

unique_ptr总结

  • unique_ptr是一种内存占用少、速度快、move-only的一种智能指针,用来管理“专属所有”语义下的动态资源;
  • 默认的销毁方式是通过delete,但也可以自定义销毁方式,根据自定义销毁方式的不同幅度增加unique_ptr的size
  • 将unique_ptr转化为shared_ptr是非常容易的;
0 0
原创粉丝点击