ScopeGuard: одним велосипедом стало меньше
2026-03-10 23:31 Diff

Теги: с++, scopeguard, std::lock_guard, mutex, scopesharedguard, scopeuniqueguard, передача владения, подсчёт ссылок, интеллектуальные указатели

Все, кто хоть раз восхищался нововведениями стандарта C++11 (давно это было, но восхищаться можно бесконечно), знают о существовании интеллектуальных указателей, которые позволяют не беспокоиться о корректной очистке памяти. Также вряд ли был обделён вниманием новый класс std::lock_guard, который позволяет не беспокоиться об освобождении мьютекса:

void superFunc() { // здесь мьютекс будет захвачен std::lock_guard<std::mutex> guard(someMutex); // тут сложная логика } // при выходе из функции мьютекc будет освобождён

Как было бы здорово распространить эту идиому на другие ресурсы, которые требуют каких-либо иных действий по окончанию использования (не освобождение памяти, например, а закрытие файла). Такой код понятнее, проще писать и сопровождать. Стоит подумать некоторое время над решением.

Пишем сами

Проблем в общем-то нет никаких. Можно реализовать свой класс ScopeGuard с шаблонными параметрами значения и пользовательской delete-функцией:

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)) { }

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; }

~ScopeGuard() { destroy(); }

ScopeGuard(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(const ScopeGuard&amp;) = delete; ScopeGuard&amp; operator=(ScopeGuard&amp;&amp;) = delete;

T&amp; get() { return m_val; }

T release() { m_isSet = false; return m_val; }

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 (...) {}; }

bool m_isSet{ true }; T m_val; F m_f; };

Дабы облегчить пользователям жизнь, можно даже написать вспомогательную функцию:

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) ); }

Использовать примерно так:

int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в guard auto fileGuard = makeScopeGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом fileGuard.get();

// на выходе из scope файл закрывается } return 0; }

Цель достигнута, можно гордиться ещё одним написанным классом общего назначения, который будут использовать наши потомки. Кладём его в utils и с гордостью коммитим в репозиторий.

Используем готовое

В предложенном выше решении всё хорошо: и работает, и красиво, и можно похвастаться перед коллегами, а может даже добавить в личное портфолио. Но что, если вдруг потребуется Guard не с передачей владения (move-семантикой), а с возможностью копирования и подсчётом ссылок? Что ж, напишем новый ScopeSharedGuard. А тот, что выше, переименуем в ScopeUniqueGuard. Кажется, что-то напоминает, да? Что если переписать наши make-функции вот так:

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)); }

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)); }

Использовать можно так же, как и в прошлый раз (только лучше, потому что теперь у нас автоматом поддерживаются обе семантики: как передача владения, так и подсчёта ссылок):

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 оба файла закрывается

return 0; }

Из достоинств такого подхода:

1. Лишним велосипедом на свете стало меньше (вернее, не стало больше);
2. Получили обе целевые семантики – владение и подсчёт ссылок.

Из недостатков:

1. Работает только с указателями;
2. Больше нет поводов для гордости;
3. Маловато для размещения в портфолио.