ScopeGuard: одним велосипедом стало меньше
Все, кто хоть раз восхищался нововведениями стандарта C++11 (давно это было, но восхищаться можно бесконечно), знают о существовании интеллектуальных указателей, которые позволяют не беспокоиться о корректной очистке памяти.
void superFunc() { // здесь мьютекс будет захвачен std::lock_guard<std::mutex> guard(someMutex); // тут сложная логика } // при выходе из функции мьютекc будет освобождён
Пишем сами
Проблем в общем-то нет никаких. Можно реализовать свой класс ScopeGuard с шаблонными параметрами значения и пользовательской delete-функцией:template<typename T, typename F> class ScopeGuard { public: template<typename TT, typename FF> ScopeGuard(TT&& value, FF&& deleter) : m_val(std::forward<TT>(value)) , m_f(std::forward<FF>(deleter)) { }ScopeGuard(ScopeGuard &&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&) = delete; ScopeGuard& operator=(const ScopeGuard&) = delete; ScopeGuard& operator=(ScopeGuard&&) = delete;
T& get() { return m_val; }
T release() { m_isSet = false; return m_val; }
template<typename TT> void reset(TT && value) { destroy(); m_val = std::forward<TT>(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<typename T, typename F> ScopeGuard<std::remove_reference_t<T>, F> makeScopeGuard(T value, F&& deleter) { return ScopeGuard<T, F>( std::forward<T>(value), std::forward<F>(deleter) ); }
Использовать примерно так:
int main(int argc, char * argv[]) { { // Открываем файл и сразу помещаем его в guard auto fileGuard = makeScopeGuard( fopen("someFile", "r"), [](FILE * hFile) { fclose(hFile); } ); // работаем с файлом fileGuard.get();// на выходе из scope файл закрывается } return 0; }
Используем готовое
В предложенном выше решении всё хорошо: и работает, и красиво, и можно похвастаться перед коллегами, а может даже добавить в личное портфолио. Но что, если вдруг потребуется Guard не с передачей владения (move-семантикой), а с возможностью копирования и подсчётом ссылок? Что ж, напишем новый ScopeSharedGuard. А тот, что выше, переименуем в ScopeUniqueGuard. Кажется, что-то напоминает, да? Что если переписать наши make-функции вот так:template<typename T, typename F> auto makeUniqueGuard(T* value, F &&deleter) { return std::unique_ptr<T, F>(value, std::forward<F>(deleter)); }template<typename T, typename F> auto makeSharedGuard(T* value, F && deleter) { return std::shared_ptr<T>(value, std::forward<F>(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. Маловато для размещения в портфолио.