當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > C語(yǔ)言與CPP編程
[導(dǎo)讀]你最喜歡的C++編程風(fēng)格慣用法是什么?

現(xiàn)代C++之手寫(xiě)智能指針

0.回顧


所有代碼還是放在倉(cāng)庫(kù)里面,歡迎star!

https://github.com/Light-City/CPlusPlusThings

前面一節(jié)編寫(xiě)了一個(gè)RAII的例子:

class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)

: ptr_(ptr)
{}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};

這個(gè)類可以完成智能指針的最基本的功能:對(duì)超出作用域的對(duì)象進(jìn)行釋放。但它缺了點(diǎn)東 西:

  • 這個(gè)類只適用于 shape 類
  • 該類對(duì)象的行為不夠像指針
  • 拷貝該類對(duì)象會(huì)引發(fā)程序行為

1.手寫(xiě)auto_ptr與scope_ptr

針對(duì)"這個(gè)類只適用于 shape 類",我們想到了模板,于是改造為:

template <typename  T>
class smater_ptr {
public:
explicit smater_ptr(
T* ptr = nullptr)

: ptr_(ptr)
{}
~smater_ptr()
{
delete ptr_;
}
T* get() const { return ptr_; }
private:
T* ptr_;
};

針對(duì)"該類對(duì)象的行為不夠像指針",我們想到了指針的基本操作有*,->,布爾表達(dá)式。

于是添加三個(gè)成員函數(shù):

template <typename  T>
class smater_ptr {
public:
...
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
...
private:
T* ptr_;
};

針對(duì)"拷貝該類對(duì)象會(huì)引發(fā)程序行為",我們想到了拷貝構(gòu)造和賦值。

現(xiàn)考慮如下調(diào)用:

smart_ptr ptr1{create_shape(shape_type::circle)};
smart_ptr ptr2{ptr1};

對(duì)于第二行,究竟應(yīng)當(dāng)讓編譯時(shí)發(fā)生錯(cuò)誤,還是可以有一個(gè)更合理的行為?我們來(lái)逐一檢查 一下各種可能性。最簡(jiǎn)單的情況顯然是禁止拷貝。我們可以使用下面的代碼:

template <typename T>
class smart_ptr {

smart_ptr(const smart_ptr&)
= delete;
smart_ptr& operator=(const smart_ptr&)
= delete;

};

當(dāng)然,也可以設(shè)為private。

禁用這兩個(gè)函數(shù)非常簡(jiǎn)單,但卻解決了一種可能出錯(cuò)的情況。否則,smart_ptr ptr2{ptr1}; 在編譯時(shí)不會(huì)出錯(cuò),但在運(yùn)行時(shí)卻會(huì)有未定義行為——由于會(huì)對(duì)同一內(nèi)存釋放兩次,通常情況下會(huì)導(dǎo)致程序崩潰。

我們是不是可以考慮在拷貝智能指針時(shí)把對(duì)象拷貝一份?不行,通常人們不會(huì)這么用,因?yàn)槭褂弥悄苤羔樀哪康木褪且獪p少對(duì)象的拷貝啊。何況,雖然我們的指針類型是 shape,但實(shí)際指向的卻應(yīng)該是 circle 或 triangle 之類的對(duì)象。在 C++ 里沒(méi)有像 Java 的clone 方法這樣的約定;一般而言,并沒(méi)有通用的方法可以通過(guò)基類的指針來(lái)構(gòu)造出一個(gè)子類的對(duì)象來(lái)。

那關(guān)鍵點(diǎn)就來(lái)了,所有權(quán)!,我們可以拷貝時(shí)轉(zhuǎn)移指針的所有權(quán)!下面實(shí)現(xiàn)便是auto_ptr的核心實(shí)現(xiàn):

template<typename T>
class auto_ptr {
public:
explicit auto_ptr(
T *ptr = nullptr)
noexcept
: ptr_(ptr)
{}

~auto_ptr() noexcept {
delete ptr_;
}
// 返回值為T&,允許*ptr=10操作
T &operator*() const noexcept { return *ptr_; }

T *operator->() const noexcept { return ptr_; }

operator bool() const noexcept { return ptr_; }

T *get() const noexcept { return ptr_; }

// 拷貝構(gòu)造,被復(fù)制放釋放原來(lái)指針的所有權(quán),交給復(fù)制方
auto_ptr(auto_ptr &other) noexcept {
ptr_ = other.release();
}

// copy and swap
auto_ptr &operator=(auto_ptr &rhs) noexcept {
// auto_ptr tmp(rhs.release());
// tmp.swap(*this);
// s上述兩行等價(jià)于下面一行
auto_ptr(rhs.release()).swap(*this);
return *this;
}

// 原來(lái)的指針釋放所有權(quán)
T *release() noexcept {
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}

void swap(auto_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 轉(zhuǎn)移指針?biāo)袡?quán)
}

private:
T *ptr_;
};

template<typename T>
void swap(auto_ptr &lhs, auto_ptr &rhs) noexcept {
lhs.swap(rhs);
}

int main() {
auto_ptr ptr1{create_shape(shape_type::circle)};
auto_ptr ptr2{ptr1};
if (ptr1.get() == nullptr && ptr2.get())
cout << "拷貝構(gòu)造:ptr1釋放了所有權(quán),ptr2獲得了所有權(quán)" << endl;
ptr1 = ptr1;

auto_ptr ptr3{create_shape(shape_type::rectangle)};
ptr1 = ptr3;

if (ptr3.get() == nullptr && ptr1.get())
cout << "賦值操作:始終只有一個(gè)對(duì)象管理一個(gè)區(qū)塊!ptr3釋放了所有權(quán),ptr1獲得了所有權(quán)" << endl;
}

上述通過(guò)copy-swap技術(shù)完成了避免自我賦值與保證了強(qiáng)異常安全!

如果你覺(jué)得這個(gè)實(shí)現(xiàn)還不錯(cuò)的話,那恭喜你,你達(dá)到了 C++ 委員會(huì)在 1998 年時(shí)的水平:上面給出的語(yǔ)義本質(zhì)上就是 C++98 的 auto_ptr 的定義。如果你覺(jué)得這個(gè)實(shí)現(xiàn)很別扭的話,也恭喜你,因?yàn)?C++ 委員會(huì)也是這么覺(jué)得的:auto_ptr 在 C++17 時(shí)已經(jīng)被正式從C++ 標(biāo)準(zhǔn)里刪除了

上面會(huì)導(dǎo)致什么問(wèn)題呢?

看一下輸出結(jié)果:

shape
circle
拷貝構(gòu)造:ptr1釋放了所有權(quán),ptr2獲得了所有權(quán)
shape
rectangle
賦值操作:始終只有一個(gè)對(duì)象管理一個(gè)區(qū)塊!ptr3釋放了所有權(quán),ptr1獲得了所有權(quán)

shape與circle實(shí)在create_shape時(shí)候輸出的,我們重點(diǎn)關(guān)注最后一句話,發(fā)現(xiàn)了一個(gè)很大的問(wèn)題:它的行為會(huì)讓程序員非常容易犯錯(cuò)。一不小心把它傳遞給另外一個(gè) auto_ptr,你就不再擁有這個(gè)對(duì)象了。

上述拷貝構(gòu)造與拷貝賦值分別如下面兩張圖所示:

圖1

圖2

針對(duì)這個(gè)問(wèn)題,在C++11標(biāo)準(zhǔn)出來(lái)之前,C++98標(biāo)準(zhǔn)中都一直只有一個(gè)智能指針auto_ptr,我們知道,這是一個(gè)失敗的設(shè)計(jì)。它的本質(zhì)是管理權(quán)的轉(zhuǎn)移,這有許多問(wèn)題。而這時(shí)就有一群人開(kāi)始擴(kuò)展C++標(biāo)準(zhǔn)庫(kù)的關(guān)于智能指針的部分,他們組成了boost社區(qū),他們負(fù)責(zé)boost庫(kù)的開(kāi)發(fā)和維護(hù)。其目的是為C++程序員提供免費(fèi)的、同行審查的、可移植的程序庫(kù)。boost庫(kù)可以和C++標(biāo)準(zhǔn)庫(kù)完美的共同工作,并且為其提供擴(kuò)展功能?,F(xiàn)在的C++11標(biāo)準(zhǔn)庫(kù)的智能指針很大程度上“借鑒”了boost庫(kù)。

boost::scoped_ptr 屬于 boost 庫(kù),定義在 namespace boost 中,包含頭文件#include可以使用。scoped_ptr 跟 auto_ptr 一樣,可以方便的管理單個(gè)堆內(nèi)存對(duì)象,特別的是,scoped_ptr 獨(dú)享所有權(quán),避免了auto_ptr惱人的幾個(gè)問(wèn)題。

scoped_ptr是一種簡(jiǎn)單粗暴的設(shè)計(jì),它本質(zhì)就是防拷貝,避免出現(xiàn)管理權(quán)的轉(zhuǎn)移。這是它的最大特點(diǎn),所以他的拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載函數(shù)都只是聲明而不定義,而且為了防止有的人在類外定義,所以將函數(shù)聲明為private。但這也是它最大的問(wèn)題所在,就是不能賦值拷貝,也就是說(shuō)功能不全。但是這種設(shè)計(jì)比較高效、簡(jiǎn)潔。沒(méi)有 release() 函數(shù),不會(huì)導(dǎo)致先前的內(nèi)存泄露問(wèn)題。下面我也將模擬實(shí)現(xiàn)scoped_ptr的管理機(jī)制(實(shí)際上就是前面提到的禁止拷貝):

template<class T>
class scoped_ptr // noncopyable
{


public:
explicit scoped_ptr(T *ptr = 0) noexcept : ptr_(ptr) {
}

~scoped_ptr() noexcept {
delete ptr_;
}

void reset(T *p = 0) noexcept {
scoped_ptr(p).swap(*this);
}

T &operator*() const noexcept {
return *ptr_;
}

T *operator->() const noexcept {
return ptr_;
}

T *get() const noexcept {
return ptr_;
}

void swap(scoped_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_);
}

private:
T *ptr_;

scoped_ptr(scoped_ptr const &);
scoped_ptr &operator=(scoped_ptr const &);
};

template<typename T>
void swap(scoped_ptr &lhs, scoped_ptr &rhs) noexcept {
lhs.swap(rhs);
}

scoped_ptr特點(diǎn)總結(jié):

1)與auto_ptr類似,采用棧上的指針去管理堆上的內(nèi)容,從而使得堆上的對(duì)象隨著棧上對(duì)象銷毀時(shí)自動(dòng)刪除;

2)scoped_ptr有著更嚴(yán)格的使用限制——不能拷貝,這也意味著scoped_ptr不能轉(zhuǎn)換其所有權(quán),所以它管理的對(duì)象不能作為函數(shù)的返回值,對(duì)象生命周期僅僅局限于一定區(qū)間(該指針?biāo)诘膡}區(qū)間,而std::auto_ptr可以);

3)由于防拷貝的特性,使其管理的對(duì)象不能共享所有權(quán),這與std::auto_ptr類似,這一特點(diǎn)使該指針簡(jiǎn)單易用,但也造成了功能的薄弱。

2.手寫(xiě)unique_ptr之子類向基類轉(zhuǎn)換

在上述auto_ptr基礎(chǔ)上,我們把拷貝構(gòu)造與拷貝賦值,改為移動(dòng)構(gòu)造與移動(dòng)賦值。

template<typename T>
class unique_ptr {
public:
explicit unique_ptr(
T *ptr = nullptr)
noexcept
: ptr_(ptr)
{}

~unique_ptr() noexcept {
delete ptr_;
}

T &operator*() const noexcept { return *ptr_; }

T *operator->() const noexcept { return ptr_; }

operator bool() const noexcept { return ptr_; }

T *get() const noexcept { return ptr_; }

unique_ptr(unique_ptr &&other) noexcept {
ptr_ = other.release();
}

// copy and swap 始終只有一個(gè)對(duì)象有管理這塊空間的權(quán)限
unique_ptr &operator=(unique_ptr rhs) noexcept {
rhs.swap(*this);
return *this;
}

// 原來(lái)的指針釋放所有權(quán)
T *release() noexcept {
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}

void swap(unique_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 轉(zhuǎn)移指針?biāo)袡?quán)
}

private:
T *ptr_;
};
template<typename T>
void swap(unique_ptr &lhs, unique_ptr &rhs) {
lhs.swap(rhs);
}

調(diào)用:

int main() {
unique_ptr ptr1{create_shape(shape_type::circle)};
// unique_ptr ptr2{ptr1}; // error
unique_ptr ptr2{std::move(ptr1)}; // ok

unique_ptr ptr3{create_shape(shape_type::rectangle)};
// ptr1 = ptr3; // error
ptr3 = std::move(ptr1); // ok
}

把拷貝構(gòu)造函數(shù)中的參數(shù)類型 unique_ptr& 改成了 unique_ptr&&;現(xiàn)在它成了移動(dòng)構(gòu)造函數(shù)。把賦值函數(shù)中的參數(shù)類型 unique_ptr& 改成了 unique_ptr,在構(gòu)造參數(shù)時(shí)直接生成新的智能指針,從而不再需要在函數(shù)體中構(gòu)造臨時(shí)對(duì)象?,F(xiàn)在賦值函數(shù)的行為是移動(dòng)還是拷貝,完全依賴于構(gòu)造參數(shù)時(shí)走的是移動(dòng)構(gòu)造還是拷貝構(gòu)造。

最后,一個(gè)circle* 是可以隱式轉(zhuǎn)換成 shape*的,但上面的 unique_ptr 卻無(wú)法自動(dòng)轉(zhuǎn)換成 unique_ptr

現(xiàn)在我們考慮兩種情況:

(1)第一種:當(dāng)我們只是在原先的移動(dòng)構(gòu)造上面添加template ,此時(shí)情況是移動(dòng)構(gòu)造變?yōu)閹0宓囊苿?dòng)構(gòu)造,可以進(jìn)行子類向基類轉(zhuǎn)換,但是與移動(dòng)構(gòu)造相關(guān)的,則調(diào)用的是默認(rèn)移動(dòng)構(gòu)造,除非是子類向基類轉(zhuǎn)換,才調(diào)用帶模板的移動(dòng)構(gòu)造。

template <typename U>
unique_ptr(unique_ptr &&other) noexcept {
ptr_ = other.release();
}

六個(gè)特殊的成員函數(shù)其生成規(guī)則如下:

  • 默認(rèn)構(gòu)造函數(shù),生成規(guī)則和C++98一樣,在用戶沒(méi)有聲明自定義的構(gòu)造函數(shù)的時(shí)候并且編譯期需要的時(shí)候生成。
  • 析構(gòu)函數(shù),生成規(guī)則和C++98一樣,在C++11中有點(diǎn)不同的是,析構(gòu)函數(shù)默認(rèn)是noexcept。
  • 拷貝構(gòu)造函數(shù),用戶自定義了移動(dòng)操作會(huì)導(dǎo)致不生成默認(rèn)的拷貝構(gòu)造函數(shù),其它和C++98的行為一致。
  • 拷貝賦值操作符,用戶自定義了移動(dòng)操作會(huì)導(dǎo)致不生成默認(rèn)的拷貝賦值操作,其它和C++98的行為一致。
  • 移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符,僅僅在沒(méi)有用戶自定義的拷貝操作,移動(dòng)操作和析構(gòu)操作的時(shí)候才會(huì)生成。

根據(jù)《Effective Modern C++》Item17 P115頁(yè)提到,當(dāng)類中含有特殊成員函數(shù)變?yōu)槟0逄厥獬蓡T函數(shù)的時(shí)候,此時(shí)不滿足上述生成規(guī)則,也就是針對(duì)當(dāng)前例子來(lái)說(shuō),編譯器會(huì)默認(rèn)生成拷貝構(gòu)造,所以此時(shí)上述main調(diào)用里面為error的都可以正常運(yùn)行!

int main() {
unique_ptr ptr1{create_shape(shape_type::circle)};
unique_ptr ptr2{ptr1}; // 由于帶模板的移動(dòng)構(gòu)造函數(shù)引發(fā)編譯器會(huì)默認(rèn)生成拷貝構(gòu)造
if (ptr1.get() != nullptr) // bitwise copy 此時(shí)ptr1不為NULL
ptr2.get()->print();

unique_ptr ptr2_2{std::move(ptr1)}; // 調(diào)用的是默認(rèn)的移動(dòng)構(gòu)造,而不是帶模板的移動(dòng)構(gòu)造 bitwise move
if (ptr2_2.get() != nullptr && ptr1.get() != nullptr) // ptr1 不為空
ptr2_2.get()->print();

unique_ptr ptr3{create_shape(shape_type::rectangle)};
ptr1 = ptr3; // ok 根據(jù)形參先調(diào)用默認(rèn)拷貝,再調(diào)用拷貝賦值
ptr3 = std::move(ptr1); // ok 根據(jù)形參先調(diào)用默認(rèn)移動(dòng)構(gòu)造,而不是帶參數(shù)的移動(dòng)構(gòu)造,再調(diào)用移動(dòng)賦值
unique_ptr ptr4(std::move(new circle)); // ok 調(diào)用帶模板的移動(dòng)構(gòu)造
}

調(diào)用與結(jié)果如上代碼所示。

(2)第二種:移動(dòng)構(gòu)造與帶模板的移動(dòng)構(gòu)造同時(shí)存在,可以完成子類向基類的轉(zhuǎn)換,此時(shí)是滿足上述生成規(guī)則,此時(shí)不會(huì)生成拷貝函數(shù)!

int main() {
unique_ptr ptr1{create_shape(shape_type::circle)};
// unique_ptr ptr2{ptr1}; // error
unique_ptr ptr2_2{std::move(ptr1)}; // ok
if (ptr2_2.get() != nullptr && ptr1.get() == nullptr)
ptr2_2.get()->print();

unique_ptr ptr3{create_shape(shape_type::rectangle)};
// ptr1 = ptr3; // error
ptr3 = std::move(ptr1); // ok
// unique_ptr cl{create_shape(shape_type::circle)}; // error 因?yàn)閏reate_shape返回的是shape 不能基類轉(zhuǎn)子類
unique_ptr cl{new circle()};
unique_ptr ptr5(std::move(cl)); // ok unique轉(zhuǎn)unique
}

小結(jié):

(1)我們需要了解子類向基類的隱式轉(zhuǎn)換,通過(guò)將移動(dòng)構(gòu)造函數(shù)變?yōu)閹0宓囊苿?dòng)構(gòu)造函數(shù),要明白兩者共存情況與只有帶模板的移動(dòng)或者其他構(gòu)造函數(shù)對(duì)編譯器生成規(guī)則的影響!上述代碼,此時(shí)還不能完成基類向子類的轉(zhuǎn)換!例如:unique_ptr轉(zhuǎn)unique_ptr

(2)auto_ptr與unique_tr都是獨(dú)占所有權(quán),每次只能被單個(gè)對(duì)象所擁有,unique_ptr與auto_ptr不同的是使用移動(dòng)語(yǔ)義來(lái)顯示的編寫(xiě)。auto_ptr是可以說(shuō)你隨便賦值,但賦值完了之后原來(lái)的對(duì)象就不知不覺(jué)的報(bào)廢.搞得你莫名其妙。而unique_ptr就干脆不讓你可以隨便去復(fù)制,賦值.如果實(shí)在想傳個(gè)值就哪里,顯式的說(shuō)明內(nèi)存轉(zhuǎn)移std:move一下。然后這樣傳值完了之后,之前的對(duì)象也同樣報(bào)廢了.只不過(guò)整個(gè)move你讓明顯的知道這樣操作后會(huì)導(dǎo)致之前的unique_ptr對(duì)象失效。scope_ptr則是直接不允許拷貝。由于防拷貝的特性,使其管理的對(duì)象不能共享所有權(quán)。

3.shared_ptr之引用計(jì)數(shù)

unique_ptr 算是一種較為安全的智能指針了。但是,一個(gè)對(duì)象只能被單個(gè) unique_ptr所擁有,這顯然不能滿足所有使用場(chǎng)合的需求。一種常見(jiàn)的情況是,多個(gè)智能指針同時(shí)擁有一個(gè)對(duì)象;當(dāng)它們?nèi)慷际r(shí),這個(gè)對(duì)象也同時(shí)會(huì)被刪除。這也就是 shared_ptr 了。

兩者區(qū)別如下:

多個(gè)shared_ptr不僅共享一個(gè)對(duì)象,同時(shí)還得共享同一個(gè)計(jì)數(shù)。當(dāng)最后一個(gè)指向?qū)ο?和共享計(jì)數(shù))的shared_ptr析構(gòu)時(shí),它需要?jiǎng)h除對(duì)象和共享計(jì)數(shù)。

首先需要一個(gè)共享計(jì)數(shù)的實(shí)現(xiàn):

class shared_count {
public:
shared_count() : count_(1) {

}

// 增加計(jì)數(shù)
void add_count() {
++count_;
}

// 減少計(jì)數(shù)
long reduce_count() {
return --count_;
}

// 獲取當(dāng)前計(jì)數(shù)
long get_count() const {
return count_;
}

private:
long count_;
};

接下來(lái)實(shí)現(xiàn)引用計(jì)數(shù)智能指針:

構(gòu)造與析構(gòu)、swap實(shí)現(xiàn)如下所示:

template<typename T>
class shared_ptr {
public:
explicit shared_ptr(
T *ptr = nullptr)
noexcept
: ptr_(ptr)
{
if (ptr) {
shared_count_ = new shared_count();
}
}

~shared_ptr() noexcept {
// 最后一個(gè)shared_ptr再去刪除對(duì)象與共享計(jì)數(shù)
// ptr_不為空且此時(shí)共享計(jì)數(shù)減為0的時(shí)候,再去刪除
if(ptr_&&!shared_count_->reduce_count()) {
delete ptr_;
delete shared_count_;
}
}

void swap(shared_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_);
swap(shared_count_,rhs.shared_count_);
}

private:
T *ptr_;
shared_count *shared_count_;
};
template<typename T>
void swap(shared_ptr &lhs, shared_ptr &rhs) noexcept {
lhs.swap(rhs);
}

之前的賦值函數(shù),編譯器可以根據(jù)調(diào)用來(lái)決定是調(diào)拷貝構(gòu)造還是移動(dòng)構(gòu)函數(shù),所以不變:

// copy and swap 始終只有一個(gè)對(duì)象有管理這塊空間的權(quán)限
shared_ptr &operator=(shared_ptr rhs) noexcept {
rhs.swap(*this);
return *this;
}

拷貝構(gòu)造與移動(dòng)構(gòu)造需要改變:

除復(fù)制指針之外,對(duì)于拷貝構(gòu)造的情況,我們需要在指針?lè)强諘r(shí)把引用數(shù)加一,并復(fù)制共享 計(jì)數(shù)的指針。對(duì)于移動(dòng)構(gòu)造的情況,我們不需要調(diào)整引用數(shù),直接把 other.ptr_ 置為 空,認(rèn)為 other 不再指向該共享對(duì)象即可

實(shí)現(xiàn)如下所示:

template<typename U>
shared_ptr(const shared_ptr &other) noexcept {
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}

template<typename U>
shared_ptr(shared_ptr &&other) noexcept {
ptr_ = other.ptr_;
if (ptr_) {
shared_count_ = other.shared_count_;
other.shared_count_ = nullptr;
}
}

當(dāng)運(yùn)行的時(shí)候,報(bào)錯(cuò):

‘circle* shared_ptr::ptr_’ is private

錯(cuò)誤原因是模板的各個(gè)實(shí)例間并不天然就有 friend 關(guān)系,因而不能互訪私有成員 ptr_shared_count_。我們需要在 smart_ptr 的定義中顯式聲明:

template<typename U>
friend class shared_ptr;

此外,在當(dāng)前引用計(jì)數(shù)實(shí)現(xiàn)中,我們應(yīng)該刪除release釋放所有權(quán)函數(shù),編寫(xiě)一個(gè)返回引用計(jì)數(shù)值的函數(shù)。

long use_count() const noexcept {
if (ptr_) {
return shared_count_->get_count();
} else {
return 0;
}
}

調(diào)用:

shared_ptr ptr1(new circle());
cout << "use count of ptr1 is " << ptr1.use_count() << endl;
shared_ptr ptr2, ptr3;
cout << "use count of ptr2 was " << ptr2.use_count() << endl;
ptr2 = ptr1; // shared_ptr隱式轉(zhuǎn)換shared_ptr 調(diào)用帶模板的拷貝構(gòu)造
// cout<<"======="<
// ptr3 = ptr2; // 調(diào)用的是編譯器生成的默認(rèn)拷貝構(gòu)造 所以引用計(jì)數(shù)不會(huì)增加 ptr3=ptr2
// cout<<"======="<
ptr3 = ptr1;
cout << "此時(shí)3個(gè)shared_ptr指向同一個(gè)資源" << endl;
cout << "use count of ptr1 is now " << ptr1.use_count() << endl;
cout << "use count of ptr2 is now " << ptr2.use_count() << endl;
cout << "use count of ptr3 is now " << ptr3.use_count() << endl;
if (ptr1)
cout << "ptr1 is not empty" << endl;
// 會(huì)先調(diào)用賦值函數(shù),由編譯器決定調(diào)用的是拷貝構(gòu)造還是移動(dòng)構(gòu)造,造出一個(gè)新的臨時(shí)對(duì)象出來(lái),臨時(shí)對(duì)象會(huì)在跳出作用域后被析構(gòu)掉。
// 在析構(gòu)函數(shù)中,會(huì)先判斷該臨時(shí)對(duì)象的是否指向資源,如果沒(méi)有,析構(gòu)結(jié)束。否則,對(duì)引用計(jì)數(shù)減1,判斷引用計(jì)數(shù)是否為0,如果為0,刪除共享引用計(jì)數(shù)指針,否則不操作。
cout << "此時(shí)2個(gè)shared_ptr指向同一個(gè)資源" << endl;
ptr2 = std::move(ptr1);
if (!ptr1 && ptr2) { // 調(diào)用的是bool重載操作符
cout << "ptr1 move to ptr2" << endl;
cout << "use count of ptr1 is now " << ptr1.use_count() << endl;
cout << "use count of ptr2 is now " << ptr2.use_count() << endl;
cout << "use count of ptr3 is now " << ptr3.use_count() << endl;
}

輸出:

shape
circle
use count of ptr1 is 1
use count of ptr2 was 0
此時(shí)3個(gè)shared_ptr指向同一個(gè)資源
use count of ptr1 is now 3
use count of ptr2 is now 3
use count of ptr3 is now 3
ptr1 is not empty
此時(shí)2個(gè)shared_ptr指向同一個(gè)資源
ptr1 move to ptr2
use count of ptr1 is now 0
use count of ptr2 is now 2
use count of ptr3 is now 2
~circle
~shape

有幾點(diǎn)注意事項(xiàng):

  • 上述代碼沒(méi)有考慮線程安全性,這里只是簡(jiǎn)化版

  • =賦值重載函數(shù)不加&,編譯器決定調(diào)用拷貝構(gòu)造還是移動(dòng)構(gòu)造,來(lái)造出一個(gè)臨時(shí)對(duì)象出來(lái)。

  • 根據(jù)前面提到的,當(dāng)類中特殊函數(shù)變?yōu)閹0宓暮瘮?shù),編譯器仍然會(huì)生成默認(rèn)拷貝構(gòu)造與默認(rèn)移動(dòng)構(gòu)造。

針對(duì)第一點(diǎn):例如:ptr2 = std::move(ptr1);

會(huì)先調(diào)用賦值函數(shù),由編譯器決定調(diào)用的是拷貝構(gòu)造還是移動(dòng)構(gòu)造,造出一個(gè)新的臨時(shí)對(duì)象出來(lái),臨時(shí)對(duì)象會(huì)在跳出作用域后被析構(gòu)掉。在析構(gòu)函數(shù)中,會(huì)先判斷該臨時(shí)對(duì)象的是否指向資源,如果沒(méi)有,析構(gòu)結(jié)束。否則,對(duì)引用計(jì)數(shù)減1,判斷引用計(jì)數(shù)是否為0,如果為0,刪除共享引用計(jì)數(shù)指針,否則不操作。

針對(duì)第二點(diǎn):

shared_ptr ptr2, ptr3;
ptr3 = ptr2; // 調(diào)用的是編譯器生成的默認(rèn)拷貝構(gòu)造 所以引用計(jì)數(shù)不會(huì)增加

兩者都是一種類型,所以在調(diào)用賦值操作后,不會(huì)調(diào)用帶模板的拷貝構(gòu)造來(lái)創(chuàng)建臨時(shí)變量,而是調(diào)用編譯器生成的默認(rèn)拷貝構(gòu)造,所以此時(shí)引用計(jì)數(shù)不會(huì)增加。

4.指針類型轉(zhuǎn)換

對(duì)應(yīng)于 C++ 里的不同的類型強(qiáng)制轉(zhuǎn):

  • dynamic_cast
  • static_cast
  • const_cast
  • reinterpret_cast

4.1 dynamic_cast

在上述unique_ptr處實(shí)現(xiàn)了子類向基類的轉(zhuǎn)換,但是卻沒(méi)有實(shí)現(xiàn)基類向子類的轉(zhuǎn)換,例如::unique_ptr轉(zhuǎn)unique_ptr

實(shí)現(xiàn)這種,需要使用dynamic_cast,實(shí)現(xiàn)如下:

首先為了實(shí)現(xiàn)這些轉(zhuǎn)換,我們需要添加構(gòu)造函數(shù),允許在對(duì)智能指針內(nèi)部的指針對(duì)象賦值時(shí),使用一個(gè)現(xiàn)有的智能指針的共享計(jì)數(shù)。

// 實(shí)現(xiàn)強(qiáng)制類型轉(zhuǎn)換需要的構(gòu)造函數(shù)
template<typename U>
shared_ptr(const shared_ptr &other, T *ptr) noexcept {
ptr_ = ptr;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}

其次,就是實(shí)現(xiàn)轉(zhuǎn)換函數(shù):

template<typename T, typename U>
shared_ptr dynamic_pointer_cast(const shared_ptr &other) noexcept {
T *ptr = dynamic_cast(other.get());
return shared_ptr(other, ptr);
}

調(diào)用:

// shape* -> circle* 使用dynamic_cast轉(zhuǎn)換后,指針為空.此時(shí)資源還是被dptr2擁有,dptr1為0
shared_ptr dptr2(new shape);
shared_ptr dptr1 = dynamic_pointer_cast(dptr2); // 基類轉(zhuǎn)子類

cout << "use count of dptr1 is now " << dptr1.use_count() << endl; // 0
cout << "use count of dptr2 is now " << dptr2.use_count() << endl; // 1

// circle* -> circle* 使用dynamic_cast轉(zhuǎn)換后,指針不為空,此時(shí)資源被兩者共同使用,引用計(jì)數(shù)為2
shared_ptr dptr3(new circle);
// shared_ptr dptr3(new circle); // 上面或者當(dāng)前行,后面輸出一樣!
shared_ptr dptr1_1 = dynamic_pointer_cast(dptr3); // 基類轉(zhuǎn)子類

cout << "use count of dptr1_1 is now " << dptr1_1.use_count() << endl; // 2
cout << "use count of dptr3 is now " << dptr3.use_count() << endl; // 2

// circle* -> circle* 使用dynamic_cast轉(zhuǎn)換后,指針不為空,此時(shí)資源被兩者共同使用,引用計(jì)數(shù)為2
shared_ptr dptr3_1(new circle);
shared_ptr dptr2_1 = dynamic_pointer_cast(dptr3_1); // 子類轉(zhuǎn)基類 上行轉(zhuǎn)換,安全!

cout << "use count of dptr2_1 is now " << dptr2_1.use_count() << endl; // 2
cout << "use count of dptr3_1 is now " << dptr3_1.use_count() << endl; // 2

dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_cast和static_cast的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast具有類型檢查的功能,比static_cast更安全。在多態(tài)類型之間的轉(zhuǎn)換主要使用dynamic_cast,因?yàn)轭愋吞峁┝诉\(yùn)行時(shí)信息。

(1)下行轉(zhuǎn)換,基類轉(zhuǎn)換為子類,例如:智能指針轉(zhuǎn)換類似于shape* 轉(zhuǎn)換為circle* 使用dynamic_cast轉(zhuǎn)換后,指針為空.此時(shí)資源還是被dptr2擁有,dptr1為0。比static_cast安全。

(2)平行轉(zhuǎn)換,指向一致的相互轉(zhuǎn)換,例如:智能指針轉(zhuǎn)換類似于circle*轉(zhuǎn)換為circle*。此時(shí)引用計(jì)數(shù)為兩者共享。

(3)上行轉(zhuǎn)換,子類轉(zhuǎn)基類,例如:智能指針轉(zhuǎn)換類似于circle*轉(zhuǎn)換為shape*,此時(shí)引用技術(shù)為兩者共享。等價(jià)于static_cast。

4.2 static_cast

同樣,編寫(xiě)如下:

template<typename T, typename U>
shared_ptr static_pointer_cast(const shared_ptr &other) noexcept {
T *ptr = static_cast(other.get());
return shared_ptr(other, ptr);
}

調(diào)用:

// shape* -> circle* 使用static_cast轉(zhuǎn)換后,指針為空  與dynamic_cast相比,不安全
shared_ptr sptr2(new shape);
shared_ptr sptr1 = static_pointer_cast(sptr2); // 基類轉(zhuǎn)子類

cout << "use count of sptr1 is now " << dptr1.use_count() << endl; // 0
cout << "use count of sptr2 is now " << dptr2.use_count() << endl; // 1

// circle* -> circle* 使用dynamic_cast轉(zhuǎn)換后,指針不為空,此時(shí)資源被兩者共同使用,引用計(jì)數(shù)為2
shared_ptr sptr3(new circle);
// shared_ptr sptr3(new circle); // 上面或者當(dāng)前行,后面輸出一樣!
shared_ptr sptr1_1 = static_pointer_cast(sptr3); // 基類轉(zhuǎn)子類

cout << "use count of sptr1_1 is now " << sptr1_1.use_count() << endl; // 2
cout << "use count of sptr3 is now " << sptr3.use_count() << endl; // 2

// circle* -> circle* 使用static_cast轉(zhuǎn)換后,指針不為空,此時(shí)資源被兩者共同使用,引用計(jì)數(shù)為2 等價(jià)于dynamic_cast
shared_ptr sptr3_1(new circle);
shared_ptr sptr2_1 = static_pointer_cast(sptr3_1); // 子類轉(zhuǎn)基類 上行轉(zhuǎn)換,安全!

cout << "use count of sptr2_1 is now " << sptr2_1.use_count() << endl; // 2
cout << "use count of sptr3_1 is now " << sptr3_1.use_count() << endl; // 2

輸出結(jié)果同上dynamic_cast,不同之處,在下行轉(zhuǎn)換的時(shí)候(基類轉(zhuǎn)子類),是不安全的!

4.3 const_cast

去掉const屬性:

template<typename T, typename U>
shared_ptr const_pointer_cast(
const shared_ptr &other) noexcept {
T *ptr = const_cast(other.get());
return shared_ptr(other, ptr);
}

調(diào)用:

shared_ptr s = const_pointer_cast(shared_ptr<const circle>(new circle));

4.4 reinterpret_cast

例如:想把一個(gè)指針轉(zhuǎn)為整數(shù),就可以用reinterpret_cast。

template<typename T, typename U>
shared_ptr reinterpret_pointer_cast(
const shared_ptr &other) noexcept {
T *ptr = reinterpret_cast(other.get());
return shared_ptr(other, ptr);
}

調(diào)用:

int a = reinterpret_pointer_cast<int>(s);

參考自吳老師的《現(xiàn)代C++實(shí)戰(zhàn)30講》第二講。

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉