智能指針unique_ptr(effective modern c++筆記)

爲何要用智能指針呢?

由於裸指針存在不少問題,主要是下面這些:ios

  1. 難以區分指向的是單個對象仍是一個數組;c++

  2. 使用完指針以後沒法判斷是否應該銷燬指針,由於沒法判斷指針是否「擁有」指向的對象;數組

  3. 在已經肯定須要銷燬指針的狀況下,也沒法肯定是用delete關鍵字刪除,仍是有其餘特殊的銷燬機制,例如經過將指針傳入某個特定的銷燬函數來銷燬指針;函數

  4. 即使已經肯定了銷燬指針的方法,因爲1的緣由,仍然沒法肯定究竟是用delete(銷燬單個對象)仍是delete[](銷燬一個數組);指針

  5. 假設上述的問題都解決了,也很難保證在代碼的全部路徑中(分支結構,異常致使的跳轉),有且僅有一次銷燬指針操做;任何一條路徑遺漏均可能致使內存泄露,而銷燬屢次則會致使未定義行爲;c++11

  6. 理論上沒有方法來分辨一個指針是否處於懸掛狀態;日誌

智能指針

智能指針主要是下面幾個,c++11以後就引入到標準庫成爲語言新特性了(以前是在boost庫中)code

  • 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是很是容易的;

相關文章
相關標籤/搜索