понедельник, 11 мая 2015 г.

RAII вокруг C в одну строку

Работая C++ программистом я очень часто сталкиваюсь с библиотеками написанными на C. В этом языке (в отличии от C++) есть простой и удобный способ поддерживать прямую ABI совместимость библиотек. В публичных хедерах имеем только forward-declaration структуры и функции принимающие её через указатель. А все её потроха оставляем исключительно внутри библиотеки. Так работает libevent, sqlite и очень многие другие беблиотеки написанные на C. Шаблон pImpl стремиться скопировать эту модель достижения стабильности бинарного интерфейса в C++, но не закрывает всех проблем.

В результате такого подхода в коде библиотек есть набор структур неизвестной природы, набор функций для создания и инициализации этих структур, а так же набор функций для их уничтожения. Как не сложно заметить, тут есть контсруктор, деструктор и класс. В большинстве случаев в работе с такими библиотеками из C++ всегдя заводятся классы оборачивающие эти C-шные интерфейсы, так как ручной вызов функций 'деструкторов' может быть пропущен при пролёте исключений. При этом пишется много тривиального нудного кода. Но...

В C++11 появляетя удобный класс которые способен устранить необходимость написания таких RAII обёрток для каждой новой сущности, это std::unique_ptr. Так на хабре можно найти пример exception-safe обёртки вокруг libevent'овского веб сервера использующего только unique_ptr для своевременной чистки ресурсов. Правда код при этом получается не особо читабельным, ибо всюду мы видим множество длинных специализаций этого шаблонного типа использующих decltype для проведение сеансов гадания по руке указателю на функцию с целью выведения её типа.

Но всю эту магию можно скрыть воспользовавшись автоопределением типа шаблонных параметров по аргументам для шаблонных функций. Согласитесь, такой код:

auto dir = scoped(::opendir(path), ::closedir);  
for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
}  
выглядит куда понятней чем такой:
 std::unique_ptr<DIR, decltype(::closedir)> dir{::opendir(path), ::closedir};  
 for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
 }  
Определение переменной dir в первом случае компакто и мгновенно разбирается глазами. Да и функция scoped пишется один раз и всегда спокойно кочует из проекта в проект позволяя в одну строку прямо по месту создавать RAII обёртки вокруг C-шных структур:
 template<typename T, typename Del = std::default_delete<T>>  
 inline auto scoped(T *t, Del &&del = std::default_delete<T>()) {  
   return std::unique_ptr<T, Del>{t, del};  
 }  

Комментариев нет: