0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: с++, scopeguard, std::lock_guard, mutex, scopesharedguard, scopeuniqueguard, передача владения, подсчёт ссылок, интеллектуальные указатели</p>
1
<p>Теги: с++, scopeguard, std::lock_guard, mutex, scopesharedguard, scopeuniqueguard, передача владения, подсчёт ссылок, интеллектуальные указатели</p>
2
<p>Все, кто хоть раз восхищался нововведениями стандарта C++11 (давно это было, но восхищаться можно бесконечно), знают о существовании интеллектуальных указателей, которые позволяют не беспокоиться о корректной очистке памяти. Также вряд ли был обделён вниманием новый класс std::lock_guard, который позволяет не беспокоиться об освобождении мьютекса:</p>
2
<p>Все, кто хоть раз восхищался нововведениями стандарта C++11 (давно это было, но восхищаться можно бесконечно), знают о существовании интеллектуальных указателей, которые позволяют не беспокоиться о корректной очистке памяти. Также вряд ли был обделён вниманием новый класс std::lock_guard, который позволяет не беспокоиться об освобождении мьютекса:</p>
3
void superFunc() { // здесь мьютекс будет захвачен std::lock_guard<std::mutex> guard(someMutex); // тут сложная логика } // при выходе из функции мьютекc будет освобождён<p>Как было бы здорово распространить эту идиому на другие ресурсы, которые требуют каких-либо иных действий по окончанию использования (не освобождение памяти, например, а закрытие файла). Такой код понятнее, проще писать и сопровождать. Стоит подумать некоторое время над решением.</p>
3
void superFunc() { // здесь мьютекс будет захвачен std::lock_guard<std::mutex> guard(someMutex); // тут сложная логика } // при выходе из функции мьютекc будет освобождён<p>Как было бы здорово распространить эту идиому на другие ресурсы, которые требуют каких-либо иных действий по окончанию использования (не освобождение памяти, например, а закрытие файла). Такой код понятнее, проще писать и сопровождать. Стоит подумать некоторое время над решением.</p>
4
<h2>Пишем сами</h2>
4
<h2>Пишем сами</h2>
5
<p>Проблем в общем-то нет никаких. Можно реализовать свой класс ScopeGuard с шаблонными параметрами значения и пользовательской delete-функцией:</p>
5
<p>Проблем в общем-то нет никаких. Можно реализовать свой класс ScopeGuard с шаблонными параметрами значения и пользовательской delete-функцией:</p>
6
template&lt;typename T, typename F&gt; class ScopeGuard { public: template&lt;typename TT, typename FF&gt; ScopeGuard(TT&amp;&amp; value, FF&amp;&amp; deleter) : m_val(std::forward&lt;TT&gt;(value)) , m_f(std::forward&lt;FF&gt;(deleter)) { }<p>ScopeGuard(ScopeGuard &amp;&amp;that) : m_val(std::move(that.m_val)) , m_f(std::move(that.m_f)) , m_isSet(that.m_isSet) { that.m_isSet = false; }</p>
6
template&lt;typename T, typename F&gt; class ScopeGuard { public: template&lt;typename TT, typename FF&gt; ScopeGuard(TT&amp;&amp; value, FF&amp;&amp; deleter) : m_val(std::forward&lt;TT&gt;(value)) , m_f(std::forward&lt;FF&gt;(deleter)) { }<p>ScopeGuard(ScopeGuard &amp;&amp;that) : m_val(std::move(that.m_val)) , m_f(std::move(that.m_f)) , m_isSet(that.m_isSet) { that.m_isSet = false; }</p>
7
<p>~ScopeGuard() { destroy(); }</p>
7
<p>~ScopeGuard() { destroy(); }</p>
8
<p>ScopeGuard(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(ScopeGuard&amp;&amp;) = delete;</p>
8
<p>ScopeGuard(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(ScopeGuard&amp;&amp;) = delete;</p>
9
<p>T&amp; get() { return m_val; }</p>
9
<p>T&amp; get() { return m_val; }</p>
10
<p>T release() { m_isSet = false; return m_val; }</p>
10
<p>T release() { m_isSet = false; return m_val; }</p>
11
<p>template&lt;typename TT&gt; void reset(TT &amp;&amp; value) { destroy(); m_val = std::forward&lt;TT&gt;(value); m_isSet = true; } private: void destroy() { if (m_isSet) try { m_f(m_val); } catch (...) {}; }</p>
11
<p>template&lt;typename TT&gt; void reset(TT &amp;&amp; value) { destroy(); m_val = std::forward&lt;TT&gt;(value); m_isSet = true; } private: void destroy() { if (m_isSet) try { m_f(m_val); } catch (...) {}; }</p>
12
<p>bool m_isSet{ true }; T m_val; F m_f; };</p>
12
<p>bool m_isSet{ true }; T m_val; F m_f; };</p>
13
<h3>Дабы облегчить пользователям жизнь, можно даже написать вспомогательную функцию:</h3>
13
<h3>Дабы облегчить пользователям жизнь, можно даже написать вспомогательную функцию:</h3>
14
template&lt;typename T, typename F&gt; ScopeGuard&lt;std::remove_reference_t&lt;T&gt;, F&gt; makeScopeGuard(T value, F&amp;&amp; deleter) { return ScopeGuard&lt;T, F&gt;( std::forward&lt;T&gt;(value), std::forward&lt;F&gt;(deleter) ); }<h3>Использовать примерно так:</h3>
14
template&lt;typename T, typename F&gt; ScopeGuard&lt;std::remove_reference_t&lt;T&gt;, F&gt; makeScopeGuard(T value, F&amp;&amp; deleter) { return ScopeGuard&lt;T, F&gt;( std::forward&lt;T&gt;(value), std::forward&lt;F&gt;(deleter) ); }<h3>Использовать примерно так:</h3>
15
int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в guard auto fileGuard = makeScopeGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом fileGuard.get();<p>// на выходе из scope файл закрывается } return 0; }</p>
15
int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в guard auto fileGuard = makeScopeGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом fileGuard.get();<p>// на выходе из scope файл закрывается } return 0; }</p>
16
<p>Цель достигнута, можно гордиться ещё одним написанным классом общего назначения, который будут использовать наши потомки. Кладём его в utils и с гордостью коммитим в репозиторий.</p>
16
<p>Цель достигнута, можно гордиться ещё одним написанным классом общего назначения, который будут использовать наши потомки. Кладём его в utils и с гордостью коммитим в репозиторий.</p>
17
<h2>Используем готовое</h2>
17
<h2>Используем готовое</h2>
18
<p>В предложенном выше решении всё хорошо: и работает, и красиво, и можно похвастаться перед коллегами, а может даже добавить в личное портфолио. Но что, если вдруг потребуется Guard не с передачей владения (move-семантикой), а с возможностью копирования и подсчётом ссылок? Что ж, напишем новый ScopeSharedGuard. А тот, что выше, переименуем в ScopeUniqueGuard. Кажется, что-то напоминает, да? Что если переписать наши make-функции вот так:</p>
18
<p>В предложенном выше решении всё хорошо: и работает, и красиво, и можно похвастаться перед коллегами, а может даже добавить в личное портфолио. Но что, если вдруг потребуется Guard не с передачей владения (move-семантикой), а с возможностью копирования и подсчётом ссылок? Что ж, напишем новый ScopeSharedGuard. А тот, что выше, переименуем в ScopeUniqueGuard. Кажется, что-то напоминает, да? Что если переписать наши make-функции вот так:</p>
19
template&lt;typename T, typename F&gt; auto makeUniqueGuard(T* value, F &amp;&amp;deleter) { return std::unique_ptr&lt;T, F&gt;(value, std::forward&lt;F&gt;(deleter)); }<p>template&lt;typename T, typename F&gt; auto makeSharedGuard(T* value, F &amp;&amp; deleter) { return std::shared_ptr&lt;T&gt;(value, std::forward&lt;F&gt;(deleter)); }</p>
19
template&lt;typename T, typename F&gt; auto makeUniqueGuard(T* value, F &amp;&amp;deleter) { return std::unique_ptr&lt;T, F&gt;(value, std::forward&lt;F&gt;(deleter)); }<p>template&lt;typename T, typename F&gt; auto makeSharedGuard(T* value, F &amp;&amp; deleter) { return std::shared_ptr&lt;T&gt;(value, std::forward&lt;F&gt;(deleter)); }</p>
20
<p>Использовать можно так же, как и в прошлый раз (только лучше, потому что теперь у нас автоматом поддерживаются обе семантики: как передача владения, так и подсчёта ссылок):</p>
20
<p>Использовать можно так же, как и в прошлый раз (только лучше, потому что теперь у нас автоматом поддерживаются обе семантики: как передача владения, так и подсчёта ссылок):</p>
21
int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в unique-guard auto uniqueGuard = makeUniqueGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // Открываем файл и сразу помещаем его в shared-guard auto sharedGuard = makeSharedGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом uniqueGuard.get(); // и не забываем про второй sharedGuard.get(); }// на выходе из scope оба файла закрывается<p>return 0; }</p>
21
int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в unique-guard auto uniqueGuard = makeUniqueGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // Открываем файл и сразу помещаем его в shared-guard auto sharedGuard = makeSharedGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом uniqueGuard.get(); // и не забываем про второй sharedGuard.get(); }// на выходе из scope оба файла закрывается<p>return 0; }</p>
22
<h3>Из достоинств такого подхода:</h3>
22
<h3>Из достоинств такого подхода:</h3>
23
<p>1. Лишним велосипедом на свете стало меньше (вернее, не стало больше); 2. Получили обе целевые семантики - владение и подсчёт ссылок.</p>
23
<p>1. Лишним велосипедом на свете стало меньше (вернее, не стало больше); 2. Получили обе целевые семантики - владение и подсчёт ссылок.</p>
24
<h3>Из недостатков:</h3>
24
<h3>Из недостатков:</h3>
25
<p>1. Работает только с указателями; 2. Больше нет поводов для гордости; 3. Маловато для размещения в портфолио.</p>
25
<p>1. Работает только с указателями; 2. Больше нет поводов для гордости; 3. Маловато для размещения в портфолио.</p>
26
26