shared_ptr和動態陣列

apocelipes發表於2019-02-01

std::shared_ptr智慧指標是c++11一個相當重要的特性,可以極大地將開發者從資源申請/釋放的繁重勞動中解放出來。

然而直到c++17前std::shared_ptr都有一個嚴重的限制,那就是它並不支援動態陣列:

#include <memory>

std::shared_ptr<int[]> sp1(new int[10]()); // 錯誤,c++17前不能傳遞陣列型別作為shared_ptr的模板引數
std::unique_ptr<int[]> up1(new int[10]()); // ok, unique_ptr對此做了特化

std::shared_ptr<int> sp2(new int[10]()); // 錯誤,可以編譯,但會產生未定義行為,請不要這麼做

sp1錯誤的原因很明顯,然而sp2的就沒有那麼好找了,究其原因,是因為std::shared_ptr對非陣列型別都使用delete p釋放資源,顯然這對於new int[10]來說是不對的,對它應該使用delete [] p

其實c++17前的解決方案並不複雜,我們可以藉助std::default_delete,它用於提供對應型別的正確的delete操作:

std::shared_ptr<int> sp3(new int[10](), std::default_delete<int[]>());

現在我們提供了正確的delete操作,可以放心地使用了。

不過這麼做的缺點也是很明顯的:

  1. 我們想管理的值是int[]型別的,然而事實上傳給模板引數的是int
  2. 需要顯示提供delete functor
  3. 不能使用std::make_shared,無法保證異常安全
  4. c++17前shared_ptr未提供opreator[],所以當需要類似操作時不得不使用sp3.get()[index]的形式

事實上共享一片連續分配記憶體的需求是極為常見的,所以為了修正上述缺陷,c++17以及即將推出的c++2a對std::shared_ptr做了完善。

先說c++17的改進,shared_ptr增加了opreator[],並可以使用int[]類的陣列型別做模板引數,所以sp3的定義可以簡化了:

std::shared_ptr<int[]> sp3(new int[10]());

對於訪問分配的空間,可以將sp3.get()[index]替換為sp3[index]。看個具體的例子:

#include <iostream>
#include <memory>

int main()
{
    std::shared_ptr<int[]> sp(new int[5]());
    for (int i = 0; i < 5; ++i) {
        sp[i] = (i+1) * (i+1);
    }

    for (int i = 0; i < 5; ++i) {
        std::cout << sp[i] << std::endl;
    }
}

我們分配一個有5個int元素的動態陣列,然後分別賦值1-5的平方,然後輸出:

g++ -Wall -std=c++17 test.cpp
./a.out

1
4
9
16
25

使用被極大得簡化了,然而還是有點問題,那就是無法使用std::make_shared,而我們除非指定自己的delete functor,否則我們應該儘量使用std::make_shared

所以c++20對此做了改進:

auto up2 = std::make_unique<int[]>(10); // 從c++14開始,分配一個管理有10個int元素的動態陣列的unique_ptr

// c++2a中你可以這樣寫,與上一句相似,只不過返回的是shared_ptr
auto sp3 = std::make_shared<int[]>(10);

在我的編譯器上(GCC 8.2.1)還不能支援這一特性,所以很遺憾得不能提供演示了。

不過等c++2a(很可能就叫c++20)釋出後std::shared_ptr就能安全而便捷地管理動態陣列了。

相關文章