日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

get方法报空指针_智能指针shared_ptr踩坑笔记

發(fā)布時間:2025/3/20 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 get方法报空指针_智能指针shared_ptr踩坑笔记 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

平時寫代碼一直避免使用指針,但在某些場景下指針的使用還是有必要的。最近在項目中簡單使用了一下智能指針(shared_ptr),結(jié)果踩了不少坑,差點(diǎn)就爬不出來了。痛定思痛抱著《Cpp Primer》啃了兩天,看書的時候才發(fā)現(xiàn)自己的理解和實(shí)踐很淺薄,真的是有種后背發(fā)涼的感覺。。。特地記錄下這些坑點(diǎn),且警后人(指后來的自己=。=).


寫在前面……

本次實(shí)驗(yàn)基于的數(shù)據(jù)結(jié)構(gòu)定義如下:

基類Polygon的成員_points是一個shared_ptr,指向動態(tài)分配的vector<Point>,這樣實(shí)現(xiàn)了在Polygon對象的多個拷貝之間共享相同的vector<Point>。基于Polygon實(shí)現(xiàn)了Rect和Circle兩個子類。

#include <vector> #include <string> #include <memory> #include <cassert>using namespace std;static constexpr double PI = 3.14;using coord_t = double;struct Point { coord_t x, y; };class Polygon { public:Polygon(const vector<Point> &points) :_points(make_shared<const vector<Point>>(points)) {}virtual string shape() const = 0;virtual coord_t area() const = 0;public:const shared_ptr<const vector<Point>> _points; };class Rect final : public Polygon { public:Rect(const vector<Point> &points, coord_t width, coord_t height) :Polygon(points), _width(width), _height(height) {assert(points.size() == 4);}string shape() const { return "Rect"; }coord_t area() const { return _width * _height; }private:const coord_t _width;const coord_t _height; };class Circle final : public Polygon { public:Circle(const vector<Point> &points, coord_t radius) :Polygon(points), _center(points.front()), _radius(radius) {assert(points.size() == 1);}string shape() const { return "Circle"; }coord_t area() const { return PI * _radius * _radius; }private:const Point _center;const coord_t _radius; };using polygon_ptr = shared_ptr<Polygon>;using rect_ptr = shared_ptr<Rect>;using circle_ptr = shared_ptr<Circle>;// 定義一個邊長為5的矩形和一個半徑為5的圓. static vector<Point> r_points{ {0,0},{0,5},{5,5},{5,0} }; static coord_t r_width = 5, r_height = 5; static vector<Point> c_points{ {0,0} }; static coord_t c_radius = 5;

從正確定義智能指針開始……

在項目中采用智能指針的初衷是為了實(shí)現(xiàn)多個對象之間共享數(shù)據(jù),避免拷貝造成的開銷。然而在使用的時候,我竟然連定義一個智能指針都能制造出五花八門的錯誤。。。下面分別整理了正確和錯誤的用法。

1. make_shared函數(shù):最安全的分配和使用動態(tài)內(nèi)存的方法

類似順序容器的emplace成員,make_shared用其參數(shù)來構(gòu)造給定類型的對象。可以是一般的構(gòu)造函數(shù):

shared_ptr<Rect> p1 = make_shared<Rect>(r_points, r_width, r_height);

也可以是拷貝構(gòu)造函數(shù):

Rect rect_2(r_points, r_width, r_height); shared_ptr<Rect> p2 = make_shared<Rect>(rect_2); Ps:需要說明的一點(diǎn)是,由于p2指向的對象(即*p2)是rect_2的拷貝,所以它們的_points成員指向相同的內(nèi)存,共享相同的vector<Point>。這個vector<Point>是r_points的一份拷貝,保存在動態(tài)內(nèi)存中。

2. shared_ptr和new結(jié)合使用

可以用new返回的指針來初始化智能指針:

shared_ptr<Rect> p3(new Rect(r_points, r_width, r_height));

或者將一個shared_ptr綁定到一個已經(jīng)定義的普通指針:

Rect *x = new Rect(r_points, r_width, r_height); shared_ptr<Rect> p4(x); x = nullptr; Ps:這是一種不建議的寫法。原則上當(dāng)p4綁定到x時,內(nèi)存管理的責(zé)任就交給了p4,就不應(yīng)該再使用x來訪問p4指向的內(nèi)存了。因此建議在完成綁定之后立刻將x置為空指針nullptr,避免在后續(xù)代碼中使用delete x釋放p4所指的內(nèi)存,或者又將其他智能指針綁定到x上,這都會造成同一塊內(nèi)存多次釋放的錯誤。
但這就出現(xiàn)一個尷尬的情況:程序員要時刻記得一個已經(jīng)存在的變量不能使用,這要求實(shí)在是高了點(diǎn)。。。最理想的還是不要制造出x,或者說x的存在就沒有意義。

3. 【錯誤1】試圖從raw指針隱式轉(zhuǎn)換到智能指針

shared_ptr<Rect> p5 = new Rect(r_points, r_width, r_height); // !!!

【修改】接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit的,必須使用直接初始化形式:

shared_ptr<Rect> p5(new Rect(r_points, r_width, r_height));

4. 【錯誤2】將非動態(tài)分配的內(nèi)存托管給智能指針

Rect rect_6(r_points, r_width, r_height); shared_ptr<Rect> p6(&rect_6); // !!!

這種寫法將p6指向一塊棧內(nèi)存,相當(dāng)于局部變量rect_6和p6管理了同一內(nèi)存空間,而棧內(nèi)存中的對象是編譯器負(fù)責(zé)創(chuàng)建和銷毀的,而且不能析構(gòu)一個指向非動態(tài)分配的內(nèi)存的智能指針,因此是不合理的。

【修改】創(chuàng)建智能指針時傳遞一個空的刪除器函數(shù)或者直接使用raw指針,詳見stackoverflow。正如回答中說的:There is not much point in using a shared_ptr for an automatically allocated object.

Rect rect_6(r_points, r_width, r_height); shared_ptr<Rect> p6(&rect_6, [](Rect*) {});

5. 【錯誤3】將同一份動態(tài)內(nèi)存托管給多個智能指針

Rect *xx = new Rect(r_points, r_width, r_height); shared_ptr<Rect> p7(xx); {shared_ptr<Rect> p8(xx); // !!!shared_ptr<Rect> p9(p7.get()); // !!! } xx = nullptr; Rect rect_7 = *p7;

p7、p8和p9指向了相同的動態(tài)內(nèi)存,但由于它們是相互獨(dú)立創(chuàng)建的,因此各自的引用計數(shù)都是1,即相互不知道對方的存在,認(rèn)為自己是這塊內(nèi)存的唯一管理者。當(dāng)p8、p9所在程序塊結(jié)束時,內(nèi)存被釋放,從而導(dǎo)致p7變?yōu)榭諔抑羔?#xff0c;意味著當(dāng)試圖使用p7時將發(fā)生未定義的行為;而且也存在同一內(nèi)存多次釋放的危險。

Ps:在測試中還發(fā)現(xiàn)這種多個智能指針托管同一動態(tài)內(nèi)存的情況與上文智能指針指向棧內(nèi)存的情況,二者報錯信息并不相同。

【修改】與錯誤用法2類似,在創(chuàng)建智能指針時傳遞一個空的刪除器函數(shù)即可。

Rect *xx = new Rect(r_points, r_width, r_height); shared_ptr<Rect> p7(xx); {shared_ptr<Rect> p8(xx, [](Rect*) {});shared_ptr<Rect> p9(p7.get(), [](Rect*) {}); } xx = nullptr; Rect rect_7 = *p7; 小結(jié):本質(zhì)上4和5屬于同一類型的錯誤,即同一塊內(nèi)存由多個管理者托管,但它們彼此之間又不知道對方的存在,這樣就導(dǎo)致在它們各自生命周期結(jié)束時都會釋放這塊內(nèi)存的錯誤。個人認(rèn)為,5的正確寫法在某種程度上還是可以接受的,但4是一種完全不合理的智能指針使用方式,這種情況就應(yīng)該直接使用raw指針,“只有將指向動態(tài)分配的對象的指針交給shared_ptr托管才是有意義的”。
往往這種錯誤在編譯期間沒有問題,但運(yùn)行時會報錯,因此不易排查。為了避免這種錯誤,應(yīng)該養(yǎng)成良好的編程意識,《Cpp Primer》中提到幾條基本規(guī)范,建議嚴(yán)格遵循:
1. 不使用相同的raw指針初始化(或reset)多個智能指針。
2. 不delete get()返回的指針。
3. 不使用get()初始化或reset另一個智能指針。
4. 如果你使用get()返回的指針,記住當(dāng)最后一個對應(yīng)的智能指針銷毀后,你的指針就變?yōu)闊o效了。
5. 如果你使用智能指針管理的資源不是new分配的內(nèi)存,記住傳遞給它一個刪除器。

智能指針的使用場景

《Cpp Primer》中提到程序使用動態(tài)內(nèi)存出于以下三種原因之一:

1. 程序不知道自己需要使用多少對象
2. 程序不知道所需對象的準(zhǔn)確類型
3. 程序需要在多個對象間共享數(shù)據(jù)

容器類是出于第一種原因而使用動態(tài)內(nèi)存的典型例子,而2和3的需求可以使用(智能)指針很好地滿足。

智能指針成員

基類Polygon中的_points成員是一個shared_ptr智能指針,依靠它實(shí)現(xiàn)了Polygon對象的不同拷貝之間共享相同的vector<Point>,并且此成員將記錄有多少個對象共享了相同的vector<Point>,并且能在最后一個使用者被銷毀時釋放該內(nèi)存。

Rect rect_1(r_points, r_width, r_height); cout << "rect_1 points成員地址: " << rect_1._points.get() << endl; cout << "rect_1 points引用計數(shù): " << rect_1._points.use_count() << endl;Rect rect_2 = rect_1; cout << "rect_2 points成員地址: " << rect_2._points.get() << endl; cout << "rect_2 points引用計數(shù): " << rect_2._points.use_count() << endl;

上述代碼的運(yùn)行結(jié)果:

程序需要在多個對象間共享數(shù)據(jù) →(智能)指針成員

容器與繼承

當(dāng)我們使用容器存放繼承體系中的對象時,因?yàn)椴辉试S直接在容器中保存不同類型的元素,通常必須采取間接存儲的方式,即我們實(shí)際上存放的是基類的(智能)指針,這些指針?biāo)傅膶ο罂梢允腔悓ο?#xff0c;也可以是派生類對象。當(dāng)要使用具體的對象時,要利用多態(tài)性將基類指針下行轉(zhuǎn)換為派生類指針。

vector<polygon_ptr> polygon_ptrs; polygon_ptrs.push_back(make_shared<Rect>(r_points, r_width, r_height)); polygon_ptrs.push_back(make_shared<Circle>(c_points, c_radius));//auto rect = dynamic_cast<Rect*>(polygon_ptrs.front()); // compile error //auto rect = dynamic_cast<rect_ptr>(polygon_ptrs.front()); // compile error auto rect = dynamic_pointer_cast<Rect>(polygon_ptrs.front()); // compile success cout << "polygon_ptrs.front() shape: " << rect->shape() << " area: " << rect->area() << endl; auto circle = dynamic_pointer_cast<Circle>(polygon_ptrs.back()); cout << "polygon_ptrs.back() shape: " << circle->shape() << " area: " << circle->area() << endl;

上述代碼的運(yùn)行結(jié)果:

程序不知道所需對象的準(zhǔn)確類型 → 容器中放置(智能)指針而非對象智能指針的下行轉(zhuǎn)換
1. 必須使用dynamic_pointer_cast,而不是dynamic_cast。這是因?yàn)楦缸觾煞N智能指針并非繼承關(guān)系,而是完全不同的類型。
2. 基類必須是多態(tài)類型(包含虛函數(shù))。

[Github] 代碼

項目實(shí)例均在vs2017上測試,并上傳至GitHub。

[Reference] 參考

Stack Overflow: Set shared_ptr to point existing object?stackoverflow.comC++11 shared_ptr(智能指針)詳解?www.cnblogs.com

總結(jié)

以上是生活随笔為你收集整理的get方法报空指针_智能指针shared_ptr踩坑笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。