Files
Obsidian-Main/02. PARA/03. Resources(資源)/C++17/智慧指標.md
Awin Huang 6613a9bf08 vault backup: 2022-06-06 11:24:20
Affected files:
02. PARA/03. Resources(資源)/C++17/智慧指標.md
2022-06-06 11:24:20 +08:00

4.3 KiB
Raw Blame History

unique_ptr與shared_ptr都是智慧指標箱對於原本的raw pointer智慧指標使用起來更方便也不用擔心delete的問題。

unique_ptr

unique_ptr的特點是,它保證在一個時間內,只會有一個指標的擁有者,也就是這個指標不能被複製跟移動,當unique_ptr離開它的scope時候它所擁有的pointer也隨之被delete。這讓你不用擔心memory leak的問題。 原本分配記憶體的方法假設我們有一個class叫BigBuffer

BigBuffer* bigBuf = new BigBuffer(bufferSize);
// Use buffer here
delete bigBuf;

unique_ptr

auto bigBuf = std::make_unique<BigBuffer>(bufferSize);
// Use buffer here
// bigBuf will be released when exiting scope

我們統一用std::make_unique<>這個template function來建立unique_ptr,角括號<>裡面要帶入你要建立的型別,後面的括號()就是型別的constructor使用起來跟new是一樣的。 因為std::make_unique<>裡面已經有表明型別了,所以變數就用auto就可以了,不用再寫一次型別。

一旦unique_ptr建立之後,使用起來就跟一般指標沒有兩樣,都是用->來操作:bigBuf->setXXX() or bigBuf->getXXX()。 但是別忘記unique_ptr本身還是一個local variable所以我們可以用.來操作unique_ptr,例如我們可以用.reset()重新配一個指標:

bigBuf.reset(std::make_unique<BigBuffer>(bufferSizeLarger));

這時候舊指標會自動delete然後指向新的指標如果記憶體分配有成功的話或者指向nullptr(記憶體分配失敗)。 如果單純想要釋放指標,那就單純的呼叫reset()就好。

bigBuf.reset(); // Now I'm nullptr

如果要分配陣列的話:

auto intArray = std::make_unique<int[]>(1024);

使用方式也是一樣的:

intArray[5] = 555;

不過對於陣列的操作現在更建議使用std::array

如果有什麼特殊原因讓你決定不再讓unique_ptr來幫你管理指標,可以用release()來讓出指標:

auto intArray = std::make_unique<int[]>(1024);
int* intArrayRaw = intArray.release(); // Now I don't care anymore

但是這時候呼叫delete[](或delete)的責任又回到你身上了。所以千萬不要把release()reset()搞混了。

unique_ptr不能被複製跟移動,所以下列的寫法都編不過:

auto ptr1 = std::make_unique<int>(5);
std::unique_ptr<int> ptr2(ptr1);  // Error
std::unique_ptr<int> ptr2 = ptr1; // Error

在Visual Studio 2017上錯誤訊息是這樣error C2280: 'std::unique_ptr<int,std::default_delete<int>>::unique_ptr(const std::unique_ptr<int,std::default_delete<int>> &)': attempting to reference a deleted function。 其實就是unique_ptr的copy constructor跟assignment operator都被標記為delete了。

shared_ptr

建立一個shared_ptr是使用std::make_shared()

auto myBuf = std::make_shared<BigBuffer>(bufferSize);

但是shared_ptr可以被複製與移動,這是跟unique_ptr的差別:

auto myBuf = std::make_shared<BigBuffer>(bufferSize);

std::shared_ptr<BigBuffer> bufCopy = myBuf;

現在bufCopy跟myBuf都指向同一個指標他們都可以操作這個指標

myBuf->setZero(startAddr, endAddr);
bufCopy->setOne(startAddr, endAddr);

shared_ptr內部有一個參考記數reference count來紀錄它所擁有的指標已經分享給幾個變數了只要有變數離開了他的scope參考記數就會減少反之要是像上面那樣有人複製的指標參考記數就會增加參考記數歸0的時候指標就會被釋放。

有了shared_ptr我們就不必擔心delete的責任問題

std::shared_ptr<BigBuffer> getBuffer(int32_t bufferSize) {
    return std::make_shared<BigBuffer>(bufferSize);
}

int main() {
    auto myBuf = getBuffer(1024); // new(malloc) memory
    // use myBuf

    return 0;
} // myBuf delete memory here

shared_ptr有一個問題是可以會「循環參考」cyclic references也就是share_ptr A指向另一個share_ptr B然後share_ptr B又指向share_ptr A這造成參考記數reference count不會減少而永遠無法釋出指標。這個是需要注意的。

但是shared_ptr還是讓記憶體的ㄈ