part4 智能指針
1 裸指針問題
(1) 沒指明 指向 單個對象
還是數(shù)組
(2) 沒指明 是否應(yīng)該銷毀
所指內(nèi)容
(3) delete形式必須對: delete / delete []; 否則, 結(jié)果未定義
(4) 很難確保銷毀
準(zhǔn)確地沿著代碼中的每一條路徑
(包括由 異常
導(dǎo)致的路徑)執(zhí)行一次
缺少路徑 -> 資源泄漏; 多次銷毀 -> 未定義行為
(5) 通常無法判斷
指針是否懸空
, 即 是否指向原對象
智能指針 幾乎可以做原始指針?biāo)茏龅囊磺?/code>邻悬,但錯誤使用的機會更少
2 std::auto_ptr 問題
std::auto_ptr copy 動作
(copy ctor 和 copy assignment)只在
源和目的 auto_ptr都沒用 const 修飾
時具有 move 語義
症昏; 否則, 編譯報錯
原因: copy ctor 和 copy assignment 的形參都是 non-const 引用
auto_ptr(auto_ptr& __a) throw()
: _M_ptr(__a.release() ) {}
auto_ptr&
operator=(auto_ptr& __a);
=> auto_ptr 無法用于 容器
C++98/11 容器 push_back 形參類型是 const 左值引用/ 多了 右值引用
, push_back 底層
對 引用的對象進行 copy 語義
操作 => auto_ptr 作實參調(diào) 容器 push_back
時, 編譯報錯
push_back(const value_type& __x);
void push_back (value_type&& val); // C++11 新增
3 std::unique_ptr 完成 std::auto_ptr 所做的一切, 以及更多
避免 auto_ptr 會扭曲 copy 對象的含義的問題
item18 std::unique_ptr 用于 獨享所有權(quán)的資源管理
(1) 不支持 copy 語義, 只支持 move
語義: 移動后, 源 unique_ptr 為空
(2) 編譯器不支持
把 裸指針(如 new 返回值) 直接賦值給 unique_ptr
解決: 用 reset
移動構(gòu)造/移動賦值
unique_ptr( unique_ptr&& u ) noexcept;
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
unique_ptr& operator=( unique_ptr&& r ) noexcept;
template< class U, class E >
unique_ptr&
operator=( unique_ptr<U, E>&& r ) noexcept;
unique_ptr&
operator=( std::nullptr_t ) noexcept;
(3) unique_ptr 作 STL 容器的元素
時, 不能直接傳遞, 因為不能拷貝和賦值 -> 解決: 用 std::move() 傳遞
unique_ptr<int> up(new int(20) );
vector<unique_ptr<int> > vec;
vec.push_back(std::move(up) );
(4) unique_ptr 作 STL 容器的元素時, 不能作為類的成員變量
class A
{
private:
vector<unique_ptr<int> > vec; //錯誤
};
remember
(1) unique_ptr 小父丰、快肝谭、只支持移動、獨享所有權(quán)
(2) 資源銷毀: 默認(rèn) 用 delete
; 第2模板形參可指定 用戶 Deleter
(3) std::unique_ptr 可容易蛾扇、高效地轉(zhuǎn)換為 std::shared_ptr
1 常見用法
(1) 在類層次中, 作 對象的 Factory Method 返回類型
caller 獨占 工廠返回的資源
class Investment { … };
class Stock: public Investment
{ … };
class Bond: public Investment
{ … };
class RealEstate: public Investment
{ … };
template<typename... Ts> // return std::unique_ptr
std::unique_ptr<Investment> // to an object created
makeInvestment(Ts&&... params); // from the given args
{
// ...
auto pInvestment = // pInvestment is of type std::unique_ptr<Investment>
makeInvestment( arguments );
// …
} // destroy *pInvestment
(2) 所有權(quán) 遷移
場景
路徑: 工廠
返回資源
-> 被移動到容器
中 -> 移動到對象的成員 data
中 -> 對象銷毀 -> 成員銷毀過程中: 先銷毀原資源
所有權(quán)鏈 中斷
(因 異常
等原因)時, 也能保證 原資源被銷毀: 只要保證管理資源的 unique_ptr 的 Dtor 能被觸發(fā)
2 第2模板參數(shù)可用于指定 Deleter
默認(rèn)下, 銷毀將通過 delete
進行
unique_ptr 還可以指定 Deleter
(函數(shù)/函數(shù)對象/lambda)
(1) Deleter 不含狀態(tài)
時優(yōu)先選 lambda/函數(shù)對象
auto delInvmt = [](Investment* pInvestment) // custom deleter, a lambda expression
{
makeLogEntry(pInvestment);
delete pInvestment;
};
通過基類指針 delete 子類對象
=> 基類 Dtor 必須為 virtual
class Investment {
public:
…
virtual ~Investment(); // base dtor must be virtual
…
};
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)> // return type
makeInvestment(Ts&&... params)
{
// (1) 創(chuàng)建 空 unique_ptr
std::unique_ptr<Investment, decltype(delInvmt)>
pInv(nullptr, delInvmt);
// (2) 指向合適對象
if ( /* a Stock object should be created */ )
{
pInv.reset(new Stock(std::forward<Ts>(params)...));
}
else if ( /* a Bond object should be created */ )
{
pInv.reset(new Bond(std::forward<Ts>(params)...));
}
else if ( /* a RealEstate object should be created */ )
{
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
}
// (3) 返回
return pInv;
}
(2) Deleter 含狀態(tài)
時優(yōu)先選 函數(shù)(指針)
void delInvmt2(Investment* pInvestment) // custom deleter as function
{
makeLogEntry(pInvestment);
delete pInvestment;
}
template<typename... Ts> // return type has
std::unique_ptr<Investment, // size of Investment*
void (*)(Investment*)> // plus at least size
makeInvestment(Ts&&... params); // of function pointer!
3 管理 單個對象 / 數(shù)組
unique_ptr<T> / unique_ptr<T[]>
unique_ptr 管理數(shù)組通常沒有實踐價值
std::array / std::vector / std::string 通橙林颍總是比原始數(shù)組
更好
1個例外
: 類 C 的 API
, 返回一個裸指針指向堆數(shù)組
4 unique_ptr 最吸引人的特性: 容易、高效地轉(zhuǎn)換
為 shared_ptr
工廠總是返回 unique_ptr(比 shared_ptr 高效
), client 需要時轉(zhuǎn)換
成 shared_ptr
std::shared_ptr<Investment> sp = // converts std::unique_ptr to std::shared_ptr
makeInvestment( arguments );
item19 std::shared_ptr
用于 共享所有權(quán)的資源管理
1 Deleter 不再作 第2模板形參, 而是作 Ctor 的第2參數(shù)
auto loggingDel = [](Widget *pw) // custom deleter
{
makeLogEntry(pw);
delete pw;
};
std::unique_ptr< Widget, // deleter type is
decltype(loggingDel)> // part of ptr type
upw(new Widget, loggingDel);
std::shared_ptr<Widget> // deleter type is not
spw(new Widget, loggingDel); // part of ptr type
auto customDeleter1 = [](Widget *pw) { … }; // custom deleters,
auto customDeleter2 = [](Widget *pw) { … }; // each with a different type
std::shared_ptr<Widget> pw1(new Widget, customDeleter1);
std::shared_ptr<Widget> pw2(new Widget, customDeleter2);
std::vector<std::shared_ptr<Widget>> vpw{ pw1, pw2 };
2 錯誤用法
(1) 用 裸指針 賦值給 不同的 shared_ptr: 不能 直接賦值
原因: 第2個之后的 shared_ptr 會建立新的 Control Block
=> 1份資源 對應(yīng) 多份 RC(引用計數(shù))
auto pw = new Widget; // pw is raw ptr
…
std::shared_ptr<Widget> spw1(pw, loggingDel); // create control block for *pw
…
std::shared_ptr<Widget> spw2(pw, loggingDel); // create 2nd control block for *pw!
解決: 必須通過第1個 shared_ptr 中轉(zhuǎn)
std::shared_ptr<Widget> spw1(new Widget, // direct use of new
loggingDel);
std::shared_ptr<Widget> spw2(spw1); // spw2 uses same
// control block as spw1
(2) 對象自身指針 this, 直接用于 賦給/構(gòu)造
shared_ptr -> 與 (1) 的問題本質(zhì)相同
std::vector<std::shared_ptr<Widget>> processedWidgets;
class Widget
{
public:
…
void process();
…
};
void Widget::process()
{
… // process the Widget
processedWidgets.emplace_back(this); // add it to list of
} // processed Widgets;
// this is wrong!
解決: 類繼承自 std::enable_shared_from_this
+ 工廠函數(shù) + this 換成 shared_from_this()
CRTP
class Widget: public std::enable_shared_from_this<Widget>
{
public:
// factory function that perfect-forwards args to a private ctor
template<typename... Ts>
static std::shared_ptr<Widget>
create(Ts&&... params);
…
void process(); // as before
…
private:
… // ctors
};
void Widget::process()
{
// as before, process the Widget
…
// add std::shared_ptr to current object to processedWidgets
processedWidgets.emplace_back(shared_from_this());
}
CRTP
奇怪的循環(huán)模板模式: 子類 作 基類模板實參
(1) 靜態(tài)多態(tài)
: 基類 non-virtual 接口函數(shù)
中 static_cast 強轉(zhuǎn) this
為基類指針
, 然后調(diào)子類的實現(xiàn)函數(shù)
實例化基類
時, 編譯器已經(jīng)知道
了子類的 實現(xiàn)函數(shù)聲明
template <class T>
struct Base
{
void interface()
{
// ...
static_cast<T*>(this)->implementation();
// ...
}
static void static_func()
{
// ...
T::static_sub_func();
// ...
}
};
struct Derived : Base<Derived>
{
void implementation()镀首;
static void static_sub_func();
};
(2) 子類實例創(chuàng)建和析構(gòu)獨立的計數(shù)
item20 std::weak_ptr
用于可能懸掛
的 類似 std::shared_ptr 的指針
(1) weak_ptr -> shared_ptr
[1] 構(gòu)造: 作 arg 構(gòu)造
[2] 賦值: lock()
成員函數(shù)
(2) shared_ptr -> weak_ptr
[1] 構(gòu)造: 作 arg 構(gòu)造
[2] 賦值: 作 arg 賦值
1 監(jiān)控
相應(yīng)的 std::shared_ptr 所管理的資源 是否 被銷毀
, 不影響 資源的 RC
std::weak_ptr 懸掛 == 過期( expired )
<=> 資源 被銷毀
auto spw = // after spw is constructed,
std::make_shared<Widget>(); // the pointed-to Widget's ref count (RC) is 1
…
std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget as spw.
// RC remains 1
…
spw = nullptr; // RC goes to 0, Widget is destroyed.
// wpw now dangles
if (wpw.expired() ) … // if wpw doesn't point to an object…
// true
std::shared_ptr<Widget> spw1
= wpw.lock(); // if wpw's expired, spw1 is null
auto spw2 = wpw.lock(); // same as above, but uses auto
由 weak_ptr 構(gòu)造 shared_ptr
std::shared_ptr<Widget> spw3(wpw); // if wpw's expired,
// throw std::bad_weak_ptr
2 weak_ptr + shared_ptr + unique_ptr 融合
的例子: 基于 cache(緩存) 的 快速加載
std::unique_ptr<const Widget>
loadWidget(WidgetID id);
std::shared_ptr<const Widget>
fastLoadWidget(WidgetID id)
{
// (1) 無序 map: 資源 ID, 資源 的 weak_ptr(監(jiān)控資源是否被銷毀)
static std::unordered_map<WidgetID,
std::weak_ptr<const Widget> > cacheControlBlock;
// (2) 取出資源的管理指針: 取出 指定 ID 的 weak_ptr, 賦值給 shared_ptr
auto objPtr = cacheControlBlock[id].lock(); // objPtr is std::shared_ptr
// to cached object (or null
// if object's not in cache)
// (3) 若 shared_ptr 為空(即 資源不在 cache), 加載資源(用工廠函數(shù)), 用緩存控制塊管理起來
if (!objPtr) // if not in cache
{
objPtr = loadWidget(id); // load it
cacheControlBlock[id] = objPtr; // cache it
}
// (4) 返回 資源的管理指針
return objPtr;
}
3 指針懸掛 & 循環(huán)引用
A/B 內(nèi)部都有 指針指向?qū)Ψ?/code>, 且
外部都被 shared_ptr 管理
(1) 1方(如 A)內(nèi)部應(yīng)該放 shared_ptr
(2) 另1方(如 B)內(nèi)部
放
[1] 裸指針
-> 問題: A 銷毀后, B 的指針懸掛, 而 B 檢測不到指針懸掛
[2] shared_ptr
-> 問題: 循環(huán)引用
+ A/B 的 RC = 2
=> A/B lifetime 結(jié)束
時, 由于 A/B 的 RC 只能減為 1
=> A/B 均內(nèi)存泄漏
[3] weak_ptr
-> A 的 RC = 1, B 的 RC = 2 => A/B lifetime 結(jié)束
時, A/B 的 RC 減為 0/1, A 銷毀
坟漱, B能檢測到自己的指針懸掛
=> B 的 RC 減為 0, B 銷毀
item21 std::make_unique 和 std::make_shared 優(yōu)先于直接 new
template<typename T, typename... Ts>
std::unique_ptr<T>
make_unique(Ts&&... params)
{
return std::unique_ptr<T>(
new T(std::forward<Ts>(params)...) );
}
item22 使用 Pimp
時, 在實現(xiàn)文件中定義 特殊成員函數(shù)
remember
(1) Pimp 通過 減少編譯依賴性 來 減少構(gòu)建時間
(2) unique_ptr 用于 pImpl 指針, 必須要在所屬類的 頭文件中聲明/源文件中 unique_ptr 所指類定義之后定義
特殊成員函數(shù)(Dtor + Move Ctor/Assignment 或 Copy Ctor/Assignment
)
(3) shared_ptr 沒有像 unique_ptr 的要求
// "widget.h"
class Widget
{
public:
Widget();
…
private:
std::string name;
std::vector<double> data;
Gadget g1, g2, g3; // Gadget is some user-defined type
};
1 pimp 所指 struct
作 pimp 所屬類的成員
, 在 pimp 所屬類的頭文件中 只聲明不定義
// "widget.h"
class Widget
{
public:
Widget();
~Widget(); // dtor is needed—see below
…
private:
struct Impl; // declare implementation struct
Impl *pImpl; // and pointer to it
};
pimp 所指類 在 pimp 所屬類的源文件中 定義
// "widget.cpp"
#include "widget.h" // in impl. file "widget.cpp"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl // definition of Widget::Impl
{
std::string name; // with data members formerly
std::vector<double> data; // in Widget
Gadget g1, g2, g3;
};
Widget::Widget() // allocate data members for
: pImpl(new Impl) // this Widget object
{}
Widget::~Widget() // destroy data members for
{ delete pImpl; } // this object
2 pimp 從 裸指針 換成 unique_ptr
// in "widget.h"
class Widget
{
public:
Widget();
…
private:
struct Impl;
std::unique_ptr<Impl> pImpl; // use smart pointer instead of raw pointer
};
// "widget.cpp"
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl // as before
{
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() // per Item 21, create
: pImpl(std::make_unique<Impl>()) // std::unique_ptr
{} // via std::make_unique
3 client 編譯報錯
#include "widget.h"
Widget w; // error!
原因: 對不完整的類型 sizeof 或 delete
編譯器 自動生成的 Dtor
銷毀 unique_ptr
時
[1] 先用 static_assert
保證 unique_ptr 所指對象 Widget::Impl
是完整的(Dtor 能看到 Widget::Impl 的定義)
默認(rèn) Dtor 在 頭文件中 inline
=> 看不到 源文件中 Widget::Impl 的定義
=> static_assert fail
[2] 再調(diào) unique_ptr 的 Dtor
: delete unique_ptr 管理的 cache
解決: 頭文件中顯式聲明 Dtor, 源文件中 Widget::Impl 定義之后 定義 Dtor
// "widget.h"
class Widget {
public:
Widget();
~Widget(); // declaration only
…
private: // as before
struct Impl;
std::unique_ptr<Impl> pImpl;
};
// "widget.cpp"
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl // as before
{
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() // per Item 21, create
: pImpl(std::make_unique<Impl>()) // std::unique_ptr
{}
Widget::~Widget() // ~Widget definition
{}
Widget::~Widget() = default; // same effect as above
4 Pimp 自然地支持 移到語義
, 但 聲明 Dtor 會阻止編譯器自動生成 move Ctor/Assignment
-> 解決: 顯式聲明
move Ctor/Assignment
// "widget.h"
class Widget {
public:
Widget();
~Widget();
Widget(Widget&& rhs); // declarations
Widget& operator=(Widget&& rhs); // only
…
private: // as before
struct Impl;
std::unique_ptr<Impl> pImpl;
};
// "widget.cpp"
#include <string>
…
struct Widget::Impl { … };
Widget::Widget() // as before
: pImpl(std::make_unique<Impl>() )
{}
Widget::~Widget() = default; // as before
Widget::Widget(Widget&& rhs) = default; // defini-
Widget& Widget::operator=(Widget&& rhs) = default; // tions
<=>
Widget::Widget(Widget&& rhs)
: pImpl(rhs.pImpl) {}
Widget&
Widget::operator=(Widget&& rhs)
{
pImpl = rhs.pImpl; // unique_ptr 的 移動賦值
}
5 copy 語義
class Widget { // still in "widget.h"
public:
… // other funcs, as before
Widget(const Widget& rhs); // declarations
Widget& operator=(const Widget& rhs); // only
private: // as before
struct Impl;
std::unique_ptr<Impl> pImpl;
};
#include "widget.h" // as before,
… // in "widget.cpp"
struct Widget::Impl { … }; // as before
Widget::~Widget() = default; // other funcs, as before
Widget::Widget(const Widget& rhs) // copy ctor
: pImpl(std::make_unique<Impl>(*rhs.pImpl) )
{}
Widget&
Widget::operator=(const Widget& rhs) // copy operator=
{
*pImpl = *rhs.pImpl;
return *this;
}
6 unique_ptr 換成 shared_ptr
std::shared_ptr Deleter 類型 不是 智能指針類型的一部分
=>
[1] 編譯器生成 更大的 運行時數(shù)據(jù)結(jié)構(gòu)
和 更慢的 運行時代碼
[2] 所屬類 調(diào)用其 Dtor/Copy ctor(Assignment)
中 不要求 shared_ptr 所指 對象是完整(能被看到)的
class Widget { // in "widget.h"
public:
Widget();
… // no declarations for dtor
// or move operations
private:
struct Impl;
std::shared_ptr<Impl> pImpl; // std::shared_ptr
}; // instead of std::unique_ptr
Widget w1;
auto w2(std::move(w1)); // move-construct w2
w1 = std::move(w2); // move-assign w1