<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      Fork me on GitHub

      C++23的out_ptr和inout_ptr

      c++23新增了一些智能指針適配器,用來擴(kuò)展和簡化智能指針的使用。

      這次主要介紹的是std::out_ptrstd::inout_ptr。這兩個適配器用法和實(shí)現(xiàn)都很簡單,但網(wǎng)上的文檔都比較抱歉,還缺少一些比較重要的部分,因此單開一篇文章記錄一下。

      out_ptr

      首先從功能最簡單的out_ptr講起。

      std::out_ptr其實(shí)是一個函數(shù),返回一個類型為std::out_ptr_t的智能指針適配器,函數(shù)簽名如下:

      #include <memory>
      
      template< class Pointer = void, class Smart, class... Args >
      auto out_ptr( Smart& s, Args&&... args );
      

      這個函數(shù)主要是把各種智能指針包裝成output parameter,以方便現(xiàn)有的接口使用,尤其是一些用c語言寫的函數(shù)。

      在繼續(xù)之前我們先來復(fù)習(xí)一下output parameter是什么。這東西又叫傳出參數(shù),一次就是函數(shù)會把一部分?jǐn)?shù)據(jù)寫進(jìn)自己的參數(shù)里返回給調(diào)用者。

      通過參數(shù)返回是因為c語言和c++11之前的c++不支持多值返回也沒有類似tuple這樣方便的數(shù)據(jù)結(jié)構(gòu),導(dǎo)致函數(shù)無法直接返回兩個以上的值,所以需要用一種額外的傳遞數(shù)據(jù)的方式。

      比如我在以前的博客中提到的hsearch:int hsearch_r(ENTRY item, ACTION action, ENTRY **retval, struct hsearch_data *htab)。這個函數(shù)用來在哈希表里創(chuàng)建或者查找數(shù)據(jù),查找失敗的時候會返回錯誤碼,而查找成功的時候函數(shù)返回0并把找到的數(shù)據(jù)設(shè)置給retval。這個retval就是output parameter,承載了函數(shù)除了錯誤碼之外的返回數(shù)據(jù)。

      c++里現(xiàn)在很少用指針類型作為output parameter了,但還有更本地化的做法——引用:int func(const char *name, Data &retval)

      這類函數(shù)有幾個特點(diǎn):

      1. 不在乎output parameter里有什么值
      2. 函數(shù)調(diào)用期間完全享有output parameter和其資源的所有權(quán)
      3. 函數(shù)返回后output parameter通常被設(shè)置為新值

      在c++提倡少用裸指針的今天,我們越來越習(xí)慣使用shared_ptr和unique_ptr,但不管哪種智能指針都很難直接適配上面這些函數(shù),看個例子就明白了:

      int get_data(const std::string &name, Data **retval)
      {
          if (!check_name(name)) {
              return ErrCheckFailed;
          }
          *retval = make_data(name);
          return 0;
      }
      
      // 使用裸指針
      Data *data_ptr = nullptr;
      if (auto err = get_data("name", &data_ptr); err != 0) {
          錯誤處理
      } else {
          這里可以使用data_ptr
      }
      

      使用裸指針的時候代碼比較簡單,我們再來看看使用智能指針的時候:

      std::unique_ptr<Data> resource;
      
      Data *data_ptr = nullptr;
      if (auto err = get_data("name", &data_ptr); err != 0) {
          錯誤處理
      } else {
          resource.reset(data_ptr);
          這里可以使用resource
      }
      

      代碼會變得啰嗦,而且如果我們忘記了調(diào)用reset,那么資源就可能泄漏了;還有最重要的一點(diǎn),我們主動使用了裸指針,而這正是我們想避免的。

      這時候就需要out_ptr了。out_ptr生成的適配器會先放棄智能指針持有資源的所有權(quán)并將舊資源釋放,因為如前面所說我們要調(diào)用的函數(shù)會接管資源的所有權(quán),接著構(gòu)造出的std::out_ptr_t有自動的類型轉(zhuǎn)換方法,可以把智能指針轉(zhuǎn)換成我們需要的T**交給函數(shù)使用,最后在函數(shù)調(diào)用結(jié)束之后再把新的資源設(shè)置回智能指針。

      所以上面的例子可以改成:

      std::unique_ptr<Data> resource;
      if (auto err = get_data("name", std::out_ptr(resource)); err != 0) {
          錯誤處理
      } else {
          這里可以使用resource,無需reset
      }
      

      除了代碼更簡潔,out_ptr還保證異常安全,即使在調(diào)用get_data的過程中拋出了異常,也不會出現(xiàn)資源泄漏。

      利用out_ptr我們可以在使用智能指針的同時兼容老舊接口。

      out_ptr和shared_ptr

      如果只看函數(shù)簽名,很多人會覺得out_ptr也可以直接配合std::shared_ptr使用,然而現(xiàn)實(shí)是多變的:

      struct Data {
          std::string name;
      };
      
      int get_data(const std::string &name, Data **retval)
      {
          if (name == "")
              return 1;
          *retval = new Data{name};
          return 0;
      }
      
      int main()
      {
          std::shared_ptr<Data> resource;
          if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
              std::cerr << "error\n";
          else
              std::cout << "success, name: " << resource->name << "\n";
      }
      

      上面的代碼無法通過編譯:

      $ clang++ -std=c++23 test.cpp
      
      In file included from test.cpp:2:
      In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:948:
      /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/out_ptr.h:38:17: error: static assertion failed due to requirement '!__is_specialization_v<std::shared_ptr<Data>, shared_ptr> || sizeof...(_Args) > 0': Using std::shared_ptr<> without a deleter in std::out_ptr is not supported.
         38 |   static_assert(!__is_specialization_v<_Smart, shared_ptr> || sizeof...(_Args) > 0,
            |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/out_ptr.h:93:10: note: in instantiation of template class 'std::out_ptr_t<std::shared_ptr<Data>, Data *>' requested here
         93 |   return std::out_ptr_t<_Smart, _Ptr, _Args&&...>(__s, std::forward<_Args>(__args)...);
            |          ^
      test.cpp:19:48: note: in instantiation of function template specialization 'std::out_ptr<void, std::shared_ptr<Data>>' requested here
         19 |     if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
            |                                                ^
      1 error generated.
      

      報錯雖然很長但只要關(guān)注前幾行就行了,錯誤的原因很明顯,std::shared_ptr要配合out_ptr使用就必須顯示提供deleter。

      這是因為對于std::shared_ptr,deleter并不是類型的一部分,通常是我們通過構(gòu)造函數(shù)或者reset方法穿進(jìn)去的,為了能100%正確釋放資源,我們需要手動把合適的deleter傳進(jìn)去;相對地deleter是std::unique_ptr類型的一部分,out_ptr可以直接從類型參數(shù)里得到合適的deleter從而正確釋放資源。

      這也是為什么out_ptr還有變長參數(shù),這些參數(shù)就是為了std::shared_ptr或者其他有特殊要求的類似智能指針準(zhǔn)備的。

      好在上面的代碼稍作修改就能正常使用:

      int main()
      {
          std::shared_ptr<Data> resource;
      -   if (auto err = get_data("apocelipes", std::out_ptr(resource)); err != 0)
      +   if (auto err = get_data("apocelipes", std::out_ptr(resource, std::default_delete<Data>{})); err != 0)
              std::cerr << "error\n";
          else
              std::cout << "success, name: " << resource->name << "\n";
      }
      

      std::default_delete<T>會調(diào)用delete或者delete[]來釋放資源,正好我們這里可以利用它。shared_ptr平時也默認(rèn)使用的這個。

      修改很簡單,但網(wǎng)上講這點(diǎn)的文檔不多,因此多記一筆。另外基于out_ptr會臨時轉(zhuǎn)移所有權(quán)這點(diǎn)來看,共享所有權(quán)模型的std::shared_ptr其實(shí)并不適合使用out_ptr,雖然標(biāo)準(zhǔn)沒有禁止甚至還要求額外做檢測(用于初始化shared_ptr),但我仍然建議把std::shared_ptrstd::out_ptr一起使用看做一種壞味道,盡量避免這種用例。

      inout_ptr

      inout_ptr的名字比較抽象,但只是在out_ptr的基礎(chǔ)上加了個“in”而已。它會返回一個std::inout_ptr_t類型的對象,函數(shù)簽名如下:

      #include <memory>
      
      template< class Pointer = void, class Smart, class... Args >
      auto inout_ptr( Smart& s, Args&&... args );
      

      這個“in”是指使用output parameter的函數(shù)在重新設(shè)置參數(shù)的值之前會先使用他們,因此這些函數(shù)的特點(diǎn)是:

      1. 非常在乎output parameter里有什么值,根據(jù)這些值執(zhí)行不同的操作
      2. 函數(shù)調(diào)用期間完全享有output parameter和其資源的所有權(quán)
      3. 函數(shù)返回后output parameter不變或者被設(shè)置為新值

      還是看例子,我們對Data增加一個update_data函數(shù),如果name是recreate則刪除原來的對象重新創(chuàng)建一個:

      int update_data(Data **data)
      {
          if (data == nullptr || *data == nullptr)
              return 1;
          if ((*data)->name == "recreate") {
              delete *data;
              *data = new Data{"apocelipes"};
              return 2; // 代表已修改
          }
          return 0;
      }
      

      現(xiàn)實(shí)中沒人這么寫代碼,但存在很多類似的c接口,而且我們也很難控制第三方庫的代碼質(zhì)量,難免不會遇上類似的東西。如果想在這種接口上用智能指針,那只能說有福了:

      auto resource = std::make_unique<Data>("recreate");
      
      Data *ptr = resource.get();
      resource.release(); // 釋放所有權(quán),但不釋放資源
      if (auto code = update_data(&ptr); code == 1)
          std::cerr << "error\n";
      else if (code == 2) {
          resource.reset(ptr);
          std::cout << "updated, name: " << resource->name << "\n";
      } else {
          resource.reset(ptr);
          std::cout << "updated, name: " << resource->name << "\n";
      }
      

      可以看到代碼會變得很復(fù)雜,而且一但忘記使用reset就會內(nèi)存錯誤。這時候我們就需要inout_ptr幫忙了。

      inout_ptr整體上和out_ptr差不多,都是讓出資源的所有權(quán)然后重新把函數(shù)返回的值設(shè)置回去,但還有幾個差異:

      1. 前面說過需要inout_ptr的函數(shù)是需要參數(shù)的值的,因此構(gòu)造inout_ptr_t時之后放棄資源的所有權(quán),不會像out_ptr那樣釋放資源本身
      2. 資源的釋放是調(diào)用的函數(shù)的責(zé)任,inout_ptr只會把函數(shù)返回出來的值重新設(shè)置回智能指針

      inout_ptr改寫后的代碼如下:

      auto resource = std::make_unique<Data>("recreate");
      
      if (auto code = update_data(std::inout_ptr(resource)); code == 1)
          std::cerr << "error\n";
      else if (code == 2) {
          std::cout << "updated, name: " << resource->name << "\n";
      } else {
          std::cout << "updated, name: " << resource->name << "\n";
      }
      

      代碼看起來清爽多了。

      另外雖然inout_ptr也有變長參數(shù),但標(biāo)準(zhǔn)明確規(guī)定它不能配合std::shared_ptr使用,這些參數(shù)std::unique_ptr用不上,是預(yù)留給其他的第三方的類似指針對象使用的。

      注意事項

      除了std::shared_ptr配合out_ptr使用時需要傳入deleter,還有一個注意事項。

      兩個適配器都不建議這么用:

      auto out = std::out_ptr(resource);
      func(out);
      

      因為他們都是在析構(gòu)函數(shù)里重新設(shè)置智能指針的值,如果綁定到一個局部變量或者其他存儲器的變量上,函數(shù)調(diào)用結(jié)束就無法把正確的值重新設(shè)置回智能指針,這會導(dǎo)致嚴(yán)重的內(nèi)存錯誤。

      唯一建議的用法是直接使用out_ptrinout_ptr的返回值:func(std::out_ptr(resource)),這樣函數(shù)調(diào)用結(jié)束后表達(dá)式結(jié)束,返回值作為表達(dá)式中創(chuàng)建的臨時變量會被析構(gòu),這樣智能指針的值就被正常設(shè)置了。

      盡管只要在轉(zhuǎn)換操作符上加上一點(diǎn)限制就能避免誤用,但標(biāo)準(zhǔn)考慮到了各種邊緣情形,最終沒有添加限制,所以我們只能牢記這條注意事項避免踩坑了。

      總結(jié)

      說實(shí)話這兩個適配器有很濃的給c庫函數(shù)擦屁股的意味,甚至標(biāo)準(zhǔn)文檔上直接拿fopen_s做例子了,我們看下它的函數(shù)聲明就能秒懂:errno_t fopen_s( FILE *restrict *restrict streamptr, const char *restrict filename, const char *restrict mode );

      另外這兩個適配器雖然叫智能指針適配器,但也可以對普通裸指針使用,不過我不推薦這種用法。

      最后雖然它們的用法都比較偏,但真要用的時候還都有用,所以了解一下總是沒壞處的。而且它們的源代碼也很簡單,有興趣可以看看libcxx的實(shí)現(xiàn),雖然相比其他家的有點(diǎn)啰嗦,但可讀性很強(qiáng):

      out_ptr: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/out_ptr.h

      inout_ptr: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/inout_ptr.h

      參考資料

      https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf p.643

      posted @ 2025-11-05 09:30  apocelipes  閱讀(58)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 无码一区二区三区视频| 国产精品中文字幕综合| 国产台湾黄色av一区二区| 亚洲女同在线播放一区二区| 老熟妇欲乱一区二区三区| 狠狠躁夜夜躁无码中文字幕| 久久精品国产99久久无毒不卡| 国产呦交精品免费视频| 少妇激情一区二区三区视频| 亚洲一区二区三区啪啪| 丝袜美腿亚洲综合第一页| 亚洲日产韩国一二三四区| 日韩亚洲精品国产第二页| 日韩av中文字幕有码| 保康县| 蓝山县| 人人妻人人狠人人爽天天综合网 | 99视频精品全部免费 在线| 久久亚洲精品11p| 欧美和黑人xxxx猛交视频| 日韩av片无码一区二区不卡| 亚洲精品麻豆一二三区| 国产自产av一区二区三区性色| 无码av中文一区二区三区桃花岛| 三级三级三级A级全黄| 天堂av在线一区二区| 国产午夜91福利一区二区| 日本免费观看mv免费版视频网站| 亚洲国产初高中生女av| 婷婷六月天在线| 国产精品自拍自在线播放| 国产高清在线不卡一区| 亚洲区一区二区激情文学| 国产精品青草久久久久福利99 | 亚洲精品动漫免费二区| 久久97超碰色中文字幕| 色欲av久久一区二区三区久| 精品黄色av一区二区三区| 中文字幕亚洲精品第一页| 国产成人无码久久久精品一| 亚洲天堂精品一区二区|