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. Маловато для размещения в портфолио.