关闭

[置顶] unique_ptr, effective modern c++笔记

标签: c++智能指针modern-c++
197人阅读 评论(0) 收藏 举报
分类:

为什么要用智能指针呢?

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

  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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:20434次
    • 积分:582
    • 等级:
    • 排名:千里之外
    • 原创:39篇
    • 转载:0篇
    • 译文:0篇
    • 评论:9条
    最新评论