這裡的Resouce不光指Memory,可能是FILE,或是ffmpeg那種Handle Resource Management一直都是個討論的重點,要混合在C++使用,有很多種方法 拿FILE來舉例好了
什麼都不做 1 2 3 FILE *fp = fopen(...); fclose(fp);
這種方法最直接,不用學其他額外的方法,不過常常會因為程式碼的改變,而忘記release resource這件事,因此才有其他流派生存的機會
defer 大概的程式碼長這樣,不過在C++不一定叫defer,可能叫ScopeGuard之類的東西,不過原理是一樣的
1 2 FILE *fp = fopen(...); defer([&]() { fclose(fp); });
在小規模的使用是沒問題的,當Resoruce 一多就會變得冗餘,例如
1 2 3 4 5 6 FILE *fp1 = fopen(...); defer([&]() { fclose(fp1); }); FILE *fp2 = fopen(...); defer([&]() { fclose(fp2); }); FILE *fp3 = fopen(...); defer([&]() { fclose(fp3); });
於是C++ RAII的方式出現了,有鑑於shared_ptr
耗費較多的資源,這邊的方案都是unique_ptr
為主
naive unique_ptr solution 為每個resource寫出一個Wrapper
1 2 3 4 5 6 struct FILEWrapper { FILE* f; FILEWrapper(FILE *file) : f(file) {} ~FILEWrapper() { if (f) fclose(f); } }; std ::unique_ptr <FILEWrapper> fp;
沒什麼不好,只是工作量太大,每加一種Resource就要有個Wrapper,那有沒有其他方案
unique_ptr with custrom destruction 同樣以FILE舉例,新增一個function object
1 2 3 4 5 6 7 8 #include <stdio.h> #include <memory> struct FileCloser { void operator () (FILE *f) { if (f) fclose(f); }; }; std ::unique_ptr <FILE, FileCloser> fp;
這樣看起來跟上面差不了多少 另一種方法是
1 std::unique_ptr<FILE, int(*)(FILE *)> fp(fp, fclose);
這種方式比上面那個還差
out_ptr 雖然跟上面無關,不過這也是unique_ptr的一部分,一併提出 由於API設計的關係,input需要的是double pointer 程式有些可能會變成這樣
1 2 3 4 5 std ::unique_ptr <ITEMIDLIST_ABSOLUTE, CoTaskMemFreeDeleter> pidl; ITEMIDLIST_ABSOLUTE* rawPidl;hr = SHGetIDListFromObject(item, &rawPidl); pidl.reset(rawPidl); if (FAILED(hr)) return hr;
這時候就是out_ptr使用場警
1 2 3 4 std ::unique_ptr <ITEMIDLIST_ABSOLUTE, CoTaskMemFreeDeleter> pidl;hr = SHGetIDListFromObject(item, std ::out_ptr(pidl)); if (FAILED(hr)) return hr;
雖然這是在C++23才進入標準庫,不過GitHub - soasis/out_ptr: Repository for a C++11 implementation of std::out_ptr (p1132), as a standalone library! 已經可以先嘗鮮了
template auto C++17之後,放寬template的要求 於是這樣的程式碼成為可能
1 2 3 4 template <auto destroy>struct c_resource {}; c_resource<fclose> fp;
配合上C++20的Concept之後,成為威力強大的武器 以下是從Meeting CPP 2022中節錄出來的片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 template <typename T, auto * ConstructFunction, auto * DestructFunction>struct c_resource { using pointer = T *; using const_pointer = std ::add_const_t <T> *; using element_type = T; private : using Constructor = decltype (ConstructFunction); using Destructor = decltype (DestructFunction); static_assert (std ::is_function_v<std ::remove_pointer_t <Constructor>>, "I need a C function" ); static_assert (std ::is_function_v<std ::remove_pointer_t <Destructor>>, "I need a C function" ); static constexpr Constructor construct = ConstructFunction; static constexpr Destructor destruct = DestructFunction; static constexpr T * null = c_resource_null_value<T>; struct construct_t { }; public : static constexpr construct_t constructed = {}; [[nodiscard]] constexpr c_resource () noexcept = default ; [[nodiscard]] constexpr explicit c_resource (construct_t ) noexcept requires std::is_invocable_r_v<T *, Constructor> : ptr_{ construct() } {} template <typename ... Ts> requires (sizeof ...(Ts) > 0 && std ::is_invocable_r_v<T *, Constructor, Ts...>) [[nodiscard]] constexpr explicit (sizeof ...(Ts) == 1 ) c_resource (Ts &&... Args) noexcept : ptr_ { construct(static_cast <Ts &&>(Args)...) } {} template <typename ... Ts> requires (sizeof ...(Ts) > 0 && requires (T * p, Ts... Args) { { construct(&p, Args...) } -> std ::same_as<void >; }) [[nodiscard]] constexpr explicit (sizeof ...(Ts) == 1 ) c_resource (Ts &&... Args) noexcept : ptr_ { null } { construct(&ptr_, static_cast <Ts &&>(Args)...); } template <typename ... Ts> requires (std ::is_invocable_v<Constructor, T **, Ts...>) [[nodiscard]] constexpr auto emplace (Ts &&... Args) noexcept { _destruct(ptr_); ptr_ = null; return construct(&ptr_, static_cast <Ts &&>(Args)...); } [[nodiscard]] constexpr c_resource (c_resource && other) noexcept { ptr_ = other.ptr_; other.ptr_ = null; }; constexpr c_resource & operator =(c_resource && rhs) noexcept { if (this != &rhs) { _destruct(ptr_); ptr_ = rhs.ptr_; rhs.ptr_ = null; } return *this ; }; constexpr void swap (c_resource & other) noexcept { auto ptr = ptr_; ptr_ = other.ptr_; other.ptr_ = ptr; } static constexpr bool destructible = std ::is_invocable_v<Destructor, T *> || std ::is_invocable_v<Destructor, T **>; constexpr ~c_resource() noexcept = delete ; constexpr ~c_resource() noexcept requires destructible { _destruct(ptr_); } constexpr void clear () noexcept requires destructible { _destruct(ptr_); ptr_ = null; } constexpr c_resource & operator =(std ::nullptr_t ) noexcept { clear(); return *this ; } [[nodiscard]] constexpr explicit operator bool () const noexcept { return ptr_ != null; } [[nodiscard]] constexpr bool empty () const noexcept { return ptr_ == null; } [[nodiscard]] constexpr friend bool have (const c_resource & r) noexcept { return r.ptr_ != null; } auto operator <=>(const c_resource &) = delete ; [[nodiscard]] bool operator ==(const c_resource & rhs) const noexcept { return 0 == std ::memcmp (ptr_, rhs.ptr_, sizeof (T)); } #if defined(__cpp_explicit_this_parameter) template <typename U, typename V> static constexpr bool less_const = std ::is_const_v<U> < std ::is_const_v<V>; template <typename U, typename V> static constexpr bool similar = std ::is_same_v<std ::remove_const_t <U>, T>; template <typename U, typename Self> requires (similar<U, T> && !less_const<U, Self>) [[nodiscard]] constexpr operator U *(this Self && self) noexcept { return std ::forward_like<Self>(self.ptr_); } [[nodiscard]] constexpr auto operator ->(this auto && self) noexcept { return std ::forward_like<decltype (self)>(self.ptr_); } [[nodiscard]] constexpr auto get (this auto && self) noexcept { return std ::forward_like<decltype (self)>(self.ptr_); } #else [[nodiscard]] constexpr operator pointer () noexcept { return like(*this ); } [[nodiscard]] constexpr operator const_pointer () const noexcept { return like(*this ); } [[nodiscard]] constexpr pointer operator ->() noexcept { return like(*this ); } [[nodiscard]] constexpr const_pointer operator ->() const noexcept { return like(*this ); } [[nodiscard]] constexpr pointer get () noexcept { return like(*this ); } [[nodiscard]] constexpr const_pointer get () const noexcept { return like(*this ); } private : static constexpr auto like (c_resource & self) noexcept { return self.ptr_; } static constexpr auto like (const c_resource & self) noexcept { return static_cast <const_pointer>(self.ptr_); } public :#endif constexpr void reset (pointer ptr = null) noexcept { _destruct(ptr_); ptr_ = ptr; } constexpr pointer release () noexcept { auto ptr = ptr_; ptr_ = null; return ptr; } template <auto * CleanupFunction> struct guard { using cleaner = decltype (CleanupFunction); static_assert (std ::is_function_v<std ::remove_pointer_t <cleaner>>, "I need a C function" ); static_assert (std ::is_invocable_v<cleaner, pointer>, "Please check the function" ); constexpr guard (c_resource & Obj) noexcept : ptr_ { Obj.ptr_ } {} constexpr ~guard() noexcept { if (ptr_ != null) CleanupFunction(ptr_); } private : pointer ptr_; }; private : constexpr static void _destruct(pointer & p) noexcept requires std ::is_invocable_v<Destructor, T *> { if (p != null) destruct(p); } constexpr static void _destruct(pointer & p) noexcept requires std ::is_invocable_v<Destructor, T **> { if (p != null) destruct(&p); } pointer ptr_ = null; };
幾乎修正了上面所說的痛點 使用上也只要
1 c_resource<FILE, fopen, fclose> fp;
算是目前看到最通用的解法
Coroutine solution 這算是另闢新徑的方案,RAII的方案都把release resource放在destructor中 自從C++20引進Corotuine,產生了新的可能 使用上大概會是這樣
1 2 3 4 5 6 7 8 9 10 co_resource<FILE*> usage () { FILE *fp = fopen(...); co_yield fp; fclose(fp); } void foo () { co_resource<FILE*> r = usage(); }
Reference