當前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導讀]你最喜歡的C++編程風格慣用法是什麼?

現代C++之手寫智能指針

0.回顧


所有代碼還是放在倉庫裏面,歡迎star!

//github.com/Light-City/CPlusPlusThings

前面一節編寫了一個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_;
};

這個類可以完成智能指針的最基本的功能:對超出作用域的對象進行釋放。但它缺了點東 西:

  • 這個類只適用於 shape 類
  • 該類對象的行為不夠像指針
  • 拷貝該類對象會引發程序行為

1.手寫auto_ptr與scope_ptr

針對"這個類只適用於 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_;
};

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

於是添加三個成員函數:

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_;
};

針對"拷貝該類對象會引發程序行為",我們想到了拷貝構造和賦值。

現考慮如下調用:

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

對於第二行,究竟應當讓編譯時發生錯誤,還是可以有一個更合理的行為?我們來逐一檢查 一下各種可能性。最簡單的情況顯然是禁止拷貝。我們可以使用下面的代碼:

template <typename T>
class smart_ptr {

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

};

當然,也可以設為private。

禁用這兩個函數非常簡單,但卻解決了一種可能出錯的情況。否則,smart_ptr ptr2{ptr1}; 在編譯時不會出錯,但在運行時卻會有未定義行為——由於會對同一內存釋放兩次,通常情況下會導致程序崩潰。

我們是不是可以考慮在拷貝智能指針時把對象拷貝一份?不行,通常人們不會這麼用,因為使用智能指針的目的就是要減少對象的拷貝啊。何況,雖然我們的指針類型是 shape,但實際指向的卻應該是 circle 或 triangle 之類的對象。在 C++ 裏沒有像 Java 的clone 方法這樣的約定;一般而言,並沒有通用的方法可以通過基類的指針來構造出一個子類的對象來。

那關鍵點就來了,所有權!,我們可以拷貝時轉移指針的所有權!下面實現便是auto_ptr的核心實現:

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_; }

// 拷貝構造,被複制放釋放原來指針的所有權,交給複製方
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上述兩行等價於下面一行
auto_ptr(rhs.release()).swap(*this);
return *this;
}

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

void swap(auto_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 轉移指針所有權
}

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 << "拷貝構造:ptr1釋放了所有權,ptr2獲得了所有權" << endl;
ptr1 = ptr1;

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

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

上述通過copy-swap技術完成了避免自我賦值與保證了強異常安全!

如果你覺得這個實現還不錯的話,那恭喜你,你達到了 C++ 委員會在 1998 年時的水平:上面給出的語義本質上就是 C++98 的 auto_ptr 的定義。如果你覺得這個實現很彆扭的話,也恭喜你,因為 C++ 委員會也是這麼覺得的:auto_ptr 在 C++17 時已經被正式從C++ 標準裏刪除了

上面會導致什麼問題呢?

看一下輸出結果:

shape
circle
拷貝構造:ptr1釋放了所有權,ptr2獲得了所有權
shape
rectangle
賦值操作:始終只有一個對象管理一個區塊!ptr3釋放了所有權,ptr1獲得了所有權

shape與circle實在create_shape時候輸出的,我們重點關注最後一句話,發現了一個很大的問題:它的行為會讓程序員非常容易犯錯。一不小心把它傳遞給另外一個 auto_ptr,你就不再擁有這個對象了。

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

圖1

圖2

針對這個問題,在C++11標準出來之前,C++98標準中都一直只有一個智能指針auto_ptr,我們知道,這是一個失敗的設計。它的本質是管理權的轉移,這有許多問題。而這時就有一羣人開始擴展C++標準庫的關於智能指針的部分,他們組成了boost社區,他們負責boost庫的開發和維護。其目的是為C++程序員提供免費的、同行審查的、可移植的程序庫。boost庫可以和C++標準庫完美的共同工作,並且為其提供擴展功能。現在的C++11標準庫的智能指針很大程度上“借鑑”了boost庫。

boost::scoped_ptr 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include可以使用。scoped_ptr 跟 auto_ptr 一樣,可以方便的管理單個堆內存對象,特別的是,scoped_ptr 獨享所有權,避免了auto_ptr惱人的幾個問題。

scoped_ptr是一種簡單粗暴的設計,它本質就是防拷貝,避免出現管理權的轉移。這是它的最大特點,所以他的拷貝構造函數和賦值運算符重載函數都只是聲明而不定義,而且為了防止有的人在類外定義,所以將函數聲明為private。但這也是它最大的問題所在,就是不能賦值拷貝,也就是説功能不全。但是這種設計比較高效、簡潔。沒有 release() 函數,不會導致先前的內存泄露問題。下面我也將模擬實現scoped_ptr的管理機制(實際上就是前面提到的禁止拷貝):

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特點總結:

1)與auto_ptr類似,採用棧上的指針去管理堆上的內容,從而使得堆上的對象隨着棧上對象銷燬時自動刪除;

2)scoped_ptr有着更嚴格的使用限制——不能拷貝,這也意味着scoped_ptr不能轉換其所有權,所以它管理的對象不能作為函數的返回值,對象生命週期僅僅侷限於一定區間(該指針所在的{}區間,而std::auto_ptr可以);

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

2.手寫unique_ptr之子類向基類轉換

在上述auto_ptr基礎上,我們把拷貝構造與拷貝賦值,改為移動構造與移動賦值。

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 始終只有一個對象有管理這塊空間的權限
unique_ptr &operator=(unique_ptr rhs) noexcept {
rhs.swap(*this);
return *this;
}

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

void swap(unique_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 轉移指針所有權
}

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

調用:

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
}

把拷貝構造函數中的參數類型 unique_ptr& 改成了 unique_ptr&&;現在它成了移動構造函數。把賦值函數中的參數類型 unique_ptr& 改成了 unique_ptr,在構造參數時直接生成新的智能指針,從而不再需要在函數體中構造臨時對象。現在賦值函數的行為是移動還是拷貝,完全依賴於構造參數時走的是移動構造還是拷貝構造。

最後,一個circle* 是可以隱式轉換成 shape*的,但上面的 unique_ptr 卻無法自動轉換成 unique_ptr

現在我們考慮兩種情況:

(1)第一種:當我們只是在原先的移動構造上面添加template ,此時情況是移動構造變為帶模板的移動構造,可以進行子類向基類轉換,但是與移動構造相關的,則調用的是默認移動構造,除非是子類向基類轉換,才調用帶模板的移動構造。

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

六個特殊的成員函數其生成規則如下:

  • 默認構造函數,生成規則和C++98一樣,在用户沒有聲明自定義的構造函數的時候並且編譯期需要的時候生成。
  • 析構函數,生成規則和C++98一樣,在C++11中有點不同的是,析構函數默認是noexcept。
  • 拷貝構造函數,用户自定義了移動操作會導致不生成默認的拷貝構造函數,其它和C++98的行為一致。
  • 拷貝賦值操作符,用户自定義了移動操作會導致不生成默認的拷貝賦值操作,其它和C++98的行為一致。
  • 移動構造函數和移動賦值操作符,僅僅在沒有用户自定義的拷貝操作,移動操作和析構操作的時候才會生成。

根據《Effective Modern C++》Item17 P115頁提到,當類中含有特殊成員函數變為模板特殊成員函數的時候,此時不滿足上述生成規則,也就是針對當前例子來説,編譯器會默認生成拷貝構造,所以此時上述main調用裏面為error的都可以正常運行!

int main() {
unique_ptr ptr1{create_shape(shape_type::circle)};
unique_ptr ptr2{ptr1}; // 由於帶模板的移動構造函數引發編譯器會默認生成拷貝構造
if (ptr1.get() != nullptr) // bitwise copy 此時ptr1不為NULL
ptr2.get()->print();

unique_ptr ptr2_2{std::move(ptr1)}; // 調用的是默認的移動構造,而不是帶模板的移動構造 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 根據形參先調用默認拷貝,再調用拷貝賦值
ptr3 = std::move(ptr1); // ok 根據形參先調用默認移動構造,而不是帶參數的移動構造,再調用移動賦值
unique_ptr ptr4(std::move(new circle)); // ok 調用帶模板的移動構造
}

調用與結果如上代碼所示。

(2)第二種:移動構造與帶模板的移動構造同時存在,可以完成子類向基類的轉換,此時是滿足上述生成規則,此時不會生成拷貝函數!

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 因為create_shape返回的是shape 不能基類轉子類
unique_ptr cl{new circle()};
unique_ptr ptr5(std::move(cl)); // ok unique轉unique
}

小結:

(1)我們需要了解子類向基類的隱式轉換,通過將移動構造函數變為帶模板的移動構造函數,要明白兩者共存情況與只有帶模板的移動或者其他構造函數對編譯器生成規則的影響!上述代碼,此時還不能完成基類向子類的轉換!例如:unique_ptrunique_ptr

(2)auto_ptr與unique_tr都是獨佔所有權,每次只能被單個對象所擁有,unique_ptr與auto_ptr不同的是使用移動語義來顯示的編寫。auto_ptr是可以説你隨便賦值,但賦值完了之後原來的對象就不知不覺的報廢.搞得你莫名其妙。而unique_ptr就乾脆不讓你可以隨便去複製,賦值.如果實在想傳個值就哪裏,顯式的説明內存轉移std:move一下。然後這樣傳值完了之後,之前的對象也同樣報廢了.只不過整個move你讓明顯的知道這樣操作後會導致之前的unique_ptr對象失效。scope_ptr則是直接不允許拷貝。由於防拷貝的特性,使其管理的對象不能共享所有權

3.shared_ptr之引用計數

unique_ptr 算是一種較為安全的智能指針了。但是,一個對象只能被單個 unique_ptr所擁有,這顯然不能滿足所有使用場合的需求。一種常見的情況是,多個智能指針同時擁有一個對象;當它們全部都失效時,這個對象也同時會被刪除。這也就是 shared_ptr 了。

兩者區別如下:

多個shared_ptr不僅共享一個對象,同時還得共享同一個計數。當最後一個指向對象(和共享計數)的shared_ptr析構時,它需要刪除對象和共享計數。

首先需要一個共享計數的實現:

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

}

// 增加計數
void add_count() {
++count_;
}

// 減少計數
long reduce_count() {
return --count_;
}

// 獲取當前計數
long get_count() const {
return count_;
}

private:
long count_;
};

接下來實現引用計數智能指針:

構造與析構、swap實現如下所示:

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 {
// 最後一個shared_ptr再去刪除對象與共享計數
// ptr_不為空且此時共享計數減為0的時候,再去刪除
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);
}

之前的賦值函數,編譯器可以根據調用來決定是調拷貝構造還是移動構函數,所以不變:

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

拷貝構造與移動構造需要改變:

除複製指針之外,對於拷貝構造的情況,我們需要在指針非空時把引用數加一,並複製共享 計數的指針。對於移動構造的情況,我們不需要調整引用數,直接把 other.ptr_ 置為 空,認為 other 不再指向該共享對象即可

實現如下所示:

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;
}
}

當運行的時候,報錯:

‘circle* shared_ptr::ptr_’ is private

錯誤原因是模板的各個實例間並不天然就有 friend 關係,因而不能互訪私有成員 ptr_shared_count_。我們需要在 smart_ptr 的定義中顯式聲明:

template<typename U>
friend class shared_ptr;

此外,在當前引用計數實現中,我們應該刪除release釋放所有權函數,編寫一個返回引用計數值的函數。

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

調用:

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隱式轉換shared_ptr 調用帶模板的拷貝構造
// cout<<"======="<
// ptr3 = ptr2; // 調用的是編譯器生成的默認拷貝構造 所以引用計數不會增加 ptr3=ptr2
// cout<<"======="<
ptr3 = ptr1;
cout << "此時3個shared_ptr指向同一個資源" << 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;
// 會先調用賦值函數,由編譯器決定調用的是拷貝構造還是移動構造,造出一個新的臨時對象出來,臨時對象會在跳出作用域後被析構掉。
// 在析構函數中,會先判斷該臨時對象的是否指向資源,如果沒有,析構結束。否則,對引用計數減1,判斷引用計數是否為0,如果為0,刪除共享引用計數指針,否則不操作。
cout << "此時2個shared_ptr指向同一個資源" << endl;
ptr2 = std::move(ptr1);
if (!ptr1 && ptr2) { // 調用的是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
此時3shared_ptr指向同一個資源
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
此時2shared_ptr指向同一個資源
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

有幾點注意事項:

  • 上述代碼沒有考慮線程安全性,這裏只是簡化版

  • =賦值重載函數不加&,編譯器決定調用拷貝構造還是移動構造,來造出一個臨時對象出來。

  • 根據前面提到的,當類中特殊函數變為帶模板的函數,編譯器仍然會生成默認拷貝構造與默認移動構造。

針對第一點:例如:ptr2 = std::move(ptr1);

會先調用賦值函數,由編譯器決定調用的是拷貝構造還是移動構造,造出一個新的臨時對象出來,臨時對象會在跳出作用域後被析構掉。在析構函數中,會先判斷該臨時對象的是否指向資源,如果沒有,析構結束。否則,對引用計數減1,判斷引用計數是否為0,如果為0,刪除共享引用計數指針,否則不操作。

針對第二點:

shared_ptr ptr2, ptr3;
ptr3 = ptr2; // 調用的是編譯器生成的默認拷貝構造 所以引用計數不會增加

兩者都是一種類型,所以在調用賦值操作後,不會調用帶模板的拷貝構造來創建臨時變量,而是調用編譯器生成的默認拷貝構造,所以此時引用計數不會增加。

4.指針類型轉換

對應於 C++ 裏的不同的類型強制轉:

  • dynamic_cast
  • static_cast
  • const_cast
  • reinterpret_cast

4.1 dynamic_cast

在上述unique_ptr處實現了子類向基類的轉換,但是卻沒有實現基類向子類的轉換,例如::unique_ptrunique_ptr

實現這種,需要使用dynamic_cast,實現如下:

首先為了實現這些轉換,我們需要添加構造函數,允許在對智能指針內部的指針對象賦值時,使用一個現有的智能指針的共享計數。

// 實現強制類型轉換需要的構造函數
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_;
}
}

其次,就是實現轉換函數:

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);
}

調用:

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

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轉換後,指針不為空,此時資源被兩者共同使用,引用計數為2
shared_ptr dptr3(new circle);
// shared_ptr dptr3(new circle); // 上面或者當前行,後面輸出一樣!
shared_ptr dptr1_1 = dynamic_pointer_cast(dptr3); // 基類轉子類

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轉換後,指針不為空,此時資源被兩者共同使用,引用計數為2
shared_ptr dptr3_1(new circle);
shared_ptr dptr2_1 = dynamic_pointer_cast(dptr3_1); // 子類轉基類 上行轉換,安全!

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主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。在多態類型之間的轉換主要使用dynamic_cast,因為類型提供了運行時信息。

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

(2)平行轉換,指向一致的相互轉換,例如:智能指針轉換類似於circle*轉換為circle*。此時引用計數為兩者共享。

(3)上行轉換,子類轉基類,例如:智能指針轉換類似於circle*轉換為shape*,此時引用技術為兩者共享。等價於static_cast。

4.2 static_cast

同樣,編寫如下:

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);
}

調用:

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

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轉換後,指針不為空,此時資源被兩者共同使用,引用計數為2
shared_ptr sptr3(new circle);
// shared_ptr sptr3(new circle); // 上面或者當前行,後面輸出一樣!
shared_ptr sptr1_1 = static_pointer_cast(sptr3); // 基類轉子類

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轉換後,指針不為空,此時資源被兩者共同使用,引用計數為2 等價於dynamic_cast
shared_ptr sptr3_1(new circle);
shared_ptr sptr2_1 = static_pointer_cast(sptr3_1); // 子類轉基類 上行轉換,安全!

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

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

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);
}

調用:

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

4.4 reinterpret_cast

例如:想把一個指針轉為整數,就可以用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);
}

調用:

int a = reinterpret_pointer_cast<int>(s);

參考自吳老師的《現代C++實戰30講》第二講。

免責聲明:本文內容由21ic獲得授權後發佈,版權歸原作者所有,本平台僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平台立場,如有問題,請聯繫我們,謝謝!

換一批

延伸閲讀

[嵌入式案例Show] STM32 時鐘分析

01 前言 在嵌入式系統中時鐘是其脈搏,處理器內核在時鐘驅動下完成指令執行,狀態變換等動作。外設部件在時鐘的驅動下完成各種工作,比如串口數據的發送、A/D轉換、定時器計數等等。...

關鍵字: STM32 時鐘 嵌入式

[嵌入式ARM] 盤點STM32的國產替代者(4)

應讀者要求,嵌入式ARM將繼續介紹能夠替代STM32的國產產品。 MM32是一個全球化的MCU產品,靈動微在上海設立芯片設計及運營中心,藉助上海晶圓代工、封裝測試完整產業鏈,確保靈動MCU從研發到生...

關鍵字: 國產 STM32 嵌入式

[嵌入式案例Show] 常用的排序算法(C語言)

01 前言 排序是數據處理中經常運用的一種重要運算,排序的功能是將一個數據元素(記錄)的任意序列,重新排列成一個按照一個規則有序的序列。常用的排序算法我們要熟練掌握。 02...

關鍵字: C語言 排序算法

[嵌入式雲IOT技術圈] 單片機到底是如何軟硬件結合的(深度好文)

我們通過IO和串口的軟件開發,已經體驗了嵌入式軟件開發。不知道大家有沒有疑惑,為什麼軟件能控制硬件?反正當年我學習51的時候,有這個疑惑。今天我們就暫停軟件開發,分析單片機到底是如何軟硬件結合的。並通過一個基本的程序,分析單片...

關鍵字: 單片機 IO 嵌入式

[嵌入式案例Show] 嵌入式軟件中的延時函數

1、前言 延時函數是嵌入式軟件開發中必不可少的功能函數,在每個工程裏都能找到它的蹤影。雖然看起來不起眼,但在有些時序控制的場合,使用了一點點delay,往往能解決大問...

關鍵字: 軟件 延時函數 嵌入式

熱門文章

技術子站

關閉