<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## **智能指針** C++11 引入了 3 個智能指針類型: 1. `std::unique_ptr<T>`:獨占資源所有權的指針。 2. `std::shared_ptr<T>`:共享資源所有權的指針。 3. `std::weak_ptr<T>`:共享資源的觀察者,需要和 std::shared\_ptr 一起使用,不影響資源的生命周期。 std::auto\_ptr 已被廢棄。 ## **std::unique\_ptr** 簡單說,當我們獨占資源的所有權的時候,可以使用 std::unique\_ptr 對資源進行管理——離開 unique\_ptr 對象的作用域時,會自動釋放資源。這是很基本的 RAII 思想。 std::unique\_ptr 的使用比較簡單,也是用得比較多的智能指針。這里直接看例子。 1. 使用裸指針時,要記得釋放內存。 ~~~text { int* p = new int(100); // ... delete p; // 要記得釋放內存 } ~~~ 1. 使用 std::unique\_ptr 自動管理內存。 ~~~text { std::unique_ptr<int> uptr = std::make_unique<int>(200); //... // 離開 uptr 的作用域的時候自動釋放內存 } ~~~ 1. std::unique\_ptr 是 move-only 的。 ~~~text { std::unique_ptr<int> uptr = std::make_unique<int>(200); std::unique_ptr<int> uptr1 = uptr; // 編譯錯誤,std::unique_ptr<T> 是 move-only 的 std::unique_ptr<int> uptr2 = std::move(uptr); assert(uptr == nullptr); } ~~~ 1. std::unique\_ptr 可以指向一個數組。 ~~~text { std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10); for (int i = 0; i < 10; i++) { uptr[i] = i * i; } for (int i = 0; i < 10; i++) { std::cout << uptr[i] << std::endl; } } ~~~ 1. 自定義 deleter。 ~~~text { struct FileCloser { void operator()(FILE* fp) const { if (fp != nullptr) { fclose(fp); } } }; std::unique_ptr<FILE, FileCloser> uptr(fopen("test_file.txt", "w")); } ~~~ 1. 使用 Lambda 的 deleter。 ~~~text { std::unique_ptr<FILE, std::function<void(FILE*)>> uptr( fopen("test_file.txt", "w"), [](FILE* fp) { fclose(fp); }); } ~~~ ## **std::shared\_ptr** std::shared\_ptr 其實就是對資源做引用計數——當引用計數為 0 的時候,自動釋放資源。 ~~~text { std::shared_ptr<int> sptr = std::make_shared<int>(200); assert(sptr.use_count() == 1); // 此時引用計數為 1 { std::shared_ptr<int> sptr1 = sptr; assert(sptr.get() == sptr1.get()); assert(sptr.use_count() == 2); // sptr 和 sptr1 共享資源,引用計數為 2 } assert(sptr.use_count() == 1); // sptr1 已經釋放 } // use_count 為 0 時自動釋放內存 ~~~ 和 unique\_ptr 一樣,shared\_ptr 也可以指向數組和自定義 deleter。 ~~~text { // C++20 才支持 std::make_shared<int[]> // std::shared_ptr<int[]> sptr = std::make_shared<int[]>(100); std::shared_ptr<int[]> sptr(new int[10]); for (int i = 0; i < 10; i++) { sptr[i] = i * i; } for (int i = 0; i < 10; i++) { std::cout << sptr[i] << std::endl; } } { std::shared_ptr<FILE> sptr( fopen("test_file.txt", "w"), [](FILE* fp) { std::cout << "close " << fp << std::endl; fclose(fp); }); } ~~~ ## **std::shared\_ptr 的實現原理** 一個 shared\_ptr 對象的內存開銷要比裸指針和無自定義 deleter 的 unique\_ptr 對象略大。 ~~~text std::cout << sizeof(int*) << std::endl; // 輸出 8 std::cout << sizeof(std::unique_ptr<int>) << std::endl; // 輸出 8 std::cout << sizeof(std::unique_ptr<FILE, std::function<void(FILE*)>>) << std::endl; // 輸出 40 std::cout << sizeof(std::shared_ptr<int>) << std::endl; // 輸出 16 std::shared_ptr<FILE> sptr(fopen("test_file.txt", "w"), [](FILE* fp) { std::cout << "close " << fp << std::endl; fclose(fp); }); std::cout << sizeof(sptr) << std::endl; // 輸出 16 ~~~ 無自定義 deleter 的 unique\_ptr 只需要將裸指針用 RAII 的手法封裝好就行,無需保存其它信息,所以它的開銷和裸指針是一樣的。如果有自定義 deleter,還需要保存 deleter 的信息。 shared\_ptr 需要維護的信息有兩部分: 1. 指向共享資源的指針。 2. 引用計數等共享資源的控制信息——實現上是維護一個指向控制信息的指針。 所以,shared\_ptr 對象需要保存兩個指針。shared\_ptr 的 的 deleter 是保存在控制信息中,所以,是否有自定義 deleter 不影響 shared\_ptr 對象的大小。 當我們創建一個 shared\_ptr 時,其實現一般如下: ~~~text std::shared_ptr<T> sptr1(new T); ~~~ ![](https://pic1.zhimg.com/80/v2-b0bf2ba18de7f3a364dda717a0a51c54_1440w.jpg) 復制一個 shared\_ptr : ~~~text std::shared_ptr<T> sptr2 = sptr1; ~~~ ![](https://pic3.zhimg.com/80/v2-e48536157d6181fdb97181769a7c364a_1440w.jpg) 為什么控制信息和每個 shared\_ptr 對象都需要保存指向共享資源的指針?可不可以去掉 shared\_ptr 對象中指向共享資源的指針,以節省內存開銷? 答案是:不能。 因為 shared\_ptr 對象中的指針指向的對象不一定和控制塊中的指針指向的對象一樣。 來看一個例子。 ~~~text struct Fruit { int juice; }; struct Vegetable { int fiber; }; struct Tomato : public Fruit, Vegetable { int sauce; }; // 由于繼承的存在,shared_ptr 可能指向基類對象 std::shared_ptr<Tomato> tomato = std::make_shared<Tomato>(); std::shared_ptr<Fruit> fruit = tomato; std::shared_ptr<Vegetable> vegetable = tomato; ~~~ ![](https://pic4.zhimg.com/80/v2-8dfc6105c0016d7c22e6212732faf1ef_1440w.jpg) 另外,std::shared\_ptr 支持 aliasing constructor。 ~~~text template< class Y > shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept; ~~~ Aliasing constructor,簡單說就是構造出來的 shared\_ptr 對象和參數 r 指向同一個控制塊(會影響 r 指向的資源的生命周期),但是指向共享資源的指針是參數 ptr。看下面這個例子。 ~~~text using Vec = std::vector<int>; std::shared_ptr<int> GetSPtr() { auto elts = {0, 1, 2, 3, 4}; std::shared_ptr<Vec> pvec = std::make_shared<Vec>(elts); return std::shared_ptr<int>(pvec, &(*pvec)[2]); } std::shared_ptr<int> sptr = GetSPtr(); for (int i = -2; i < 3; ++i) { printf("%d\n", sptr.get()[i]); } ~~~ ![](https://pic2.zhimg.com/80/v2-ada2e2b5dc8551bf879d77a2b484e071_1440w.jpg) 看上面的例子,使用 std::shared\_ptr 時,會涉及兩次內存分配:一次分配共享資源對象;一次分配控制塊。C++ 標準庫提供了 std::make\_shared 函數來創建一個 shared\_ptr 對象,只需要一次內存分配。 ![](https://pic1.zhimg.com/80/v2-49e619699b5c924097e027cd173df758_1440w.jpg) 這種情況下,不用通過控制塊中的指針,我們也能知道共享資源的位置——這個指針也可以省略掉。 ![](https://pic4.zhimg.com/80/v2-4d50514c2c685097588908f69c3dc027_1440w.jpg) ## **std::weak\_ptr** std::weak\_ptr 要與 std::shared\_ptr 一起使用。 一個 std::weak\_ptr 對象看做是 std::shared\_ptr 對象管理的資源的觀察者,它不影響共享資源的生命周期: 1. 如果需要使用 weak\_ptr 正在觀察的資源,可以將 weak\_ptr 提升為 shared\_ptr。 2. 當 shared\_ptr 管理的資源被釋放時,weak\_ptr 會自動變成 nullptr。\\ ~~~text void Observe(std::weak_ptr<int> wptr) { if (auto sptr = wptr.lock()) { std::cout << "value: " << *sptr << std::endl; } else { std::cout << "wptr lock fail" << std::endl; } } std::weak_ptr<int> wptr; { auto sptr = std::make_shared<int>(111); wptr = sptr; Observe(wptr); // sptr 指向的資源沒被釋放,wptr 可以成功提升為 shared_ptr } Observe(wptr); // sptr 指向的資源已被釋放,wptr 無法提升為 shared_ptr ~~~ ![](https://pic1.zhimg.com/80/v2-5f40e9422551bb244753e87ef43d1e64_1440w.jpg) 當 shared\_ptr 析構并釋放共享資源的時候,只要 weak\_ptr 對象還存在,控制塊就會保留,weak\_ptr 可以通過控制塊觀察到對象是否存活。 ![](https://pic4.zhimg.com/80/v2-f387c7135acf9101029fc4981ce2269b_1440w.jpg) ## **enable\_shared\_from\_this** 一個類的成員函數如何獲得指向自身(this)的 shared\_ptr? 看看下面這個例子有沒有問題? ~~~text class Foo { public: std::shared_ptr<Foo> GetSPtr() { return std::shared_ptr<Foo>(this); } }; auto sptr1 = std::make_shared<Foo>(); assert(sptr1.use_count() == 1); auto sptr2 = sptr1->GetSPtr(); assert(sptr1.use_count() == 1); assert(sptr2.use_count() == 1); ~~~ 上面的代碼其實會生成兩個獨立的 shared\_ptr,他們的控制塊是獨立的,最終導致一個 Foo 對象會被 delete 兩次。 ![](https://pic3.zhimg.com/80/v2-a4b338b9b6a84d56fb3ceba59b168aba_1440w.jpg) 成員函數獲取 this 的 shared\_ptr 的正確的做法是繼承 std::enable\_shared\_from\_this。 ~~~text class Bar : public std::enable_shared_from_this<Bar> { public: std::shared_ptr<Bar> GetSPtr() { return shared_from_this(); } }; auto sptr1 = std::make_shared<Bar>(); assert(sptr1.use_count() == 1); auto sptr2 = sptr1->GetSPtr(); assert(sptr1.use_count() == 2); assert(sptr2.use_count() == 2); ~~~ 一般情況下,繼承了 std::enable\_shared\_from\_this 的子類,成員變量中增加了一個指向 this 的 weak\_ptr。這個 weak\_ptr 在第一次創建 shared\_ptr 的時候會被初始化,指向 this。 ![](https://pic4.zhimg.com/80/v2-fbbbc83da3c87fe48817a4cbda49ce03_1440w.jpg) 似乎繼承了 std::enable\_shared\_from\_this 的類都被強制必須通過 shared\_ptr 進行管理。 ~~~text auto b = new Bar; auto sptr = b->shared_from_this(); ~~~ 在我的環境下(gcc 7.5.0)上面的代碼執行的時候會直接 coredump,而不是返回指向 nullptr 的 shared\_ptr: ~~~text terminate called after throwing an instance of 'std::bad_weak_ptr' what(): bad_weak_ptr ~~~ ## **小結** 智能指針,本質上是對資源所有權和生命周期管理的抽象: 1. 當資源是被獨占時,使用 std::unique\_ptr 對資源進行管理。 2. 當資源會被共享時,使用 std::shared\_ptr 對資源進行管理。 3. 使用 std::weak\_ptr 作為 std::shared\_ptr 管理對象的觀察者。 4. 通過繼承 std::enable\_shared\_from\_this 來獲取 this 的 std::shared\_ptr 對象。 ## **參考資料** 1. **[Back to Basics: Smart Pointers](https://link.zhihu.com/?target=https%3A//github.com/CppCon/CppCon2019/blob/master/Presentations/back_to_basics_smart_pointers/back_to_basics_smart_pointers__arthur_odwyer__cppcon_2019.pdf)** 2. **[unique\_ptr](https://link.zhihu.com/?target=https%3A//en.cppreference.com/w/cpp/memory/unique_ptr)** 3. **[shared\_ptr](https://link.zhihu.com/?target=https%3A//en.cppreference.com/w/cpp/memory/shared_ptr)** 4. **[weak\_ptr](https://link.zhihu.com/?target=https%3A//en.cppreference.com/w/cpp/memory/weak_ptr)** 5. **[enable\_shared\_from\_this](https://link.zhihu.com/?target=https%3A//en.cppreference.com/w/cpp/memory/enable_shared_from_this)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看