Получилось кратко и эффектно.
Например, с C-style выделением памяти:
// Аллоцируем ресурс в блоке. { ha::scoped_resource<void*, size_t> mem(::malloc, 1, ::free); ::memset(mem, 65, 1); }
При выходе из блока ресурс будет освобожден автоматически.
Или еще вот так можно захватывать владение ресурсом «файловый дескриптор»:
// Захватываем ресурс в блоке. { ha::scoped_resource<int> fd( [&filename]() { return ::open(filename.c_str(), O_RDONLY); }, ::close); assert(fd != -1); std::vector<char> buff(1024); ssize_t rc = ::read(fd, &buff[0], 1024); }
При выходе из блока ресурс будет освобожден автоматически даже после вызова, например, throw std::exception()
.
Или второй пример можно переписать даже понятней без применения лямбды:
{ ha::scoped_resource<int, char*, int> fd(::open, filename.c_str(), O_RDONLY, ::close); if (fd == -1) throw std::runtime_error(std::string("open() failed: ") + ::strerror(errno)); std::vector<char> buff(1024); ssize_t rc = ::read(fd, &buff[0], 1024); }
То есть в общем случае имеем темплейтный класс, который инстанциируется типом ресурса, а его конструктор принимает две std::functions
:initializer_t и finalizer_t
.
Между инициализатором и финализатором следуют параметры для инициализатора, которые являются частью спецификаторов шаблона.
Деструктор просто вызывает финализатор для захваченного ресурса.
Для raw-доступа к ресурсу существует оператор типа ресурса.
{ ha::scoped_resource <resource_t, param1_t, ...> resource (ititializer, param1, ..., finalizer); resource_t plain_resource = resource.operator resource_t(); }
В чем преимущество перед другими RAII реализациями враперов ресурсов?
- Инициализатор не вызывается во время редукции параметров конструктора, а в самом конструкторе. Это, например, позволяет реализовать «нормальную» передачу инициализатора, что дает возможность захвата ресурса в lazy-стиле, до первого вызова
operator resource_t()
. Еще это позволяет создавать именованные инициализаторы, тем самым переиспользуя их. - Можно явно передавать какое-либо количество параметров для инициализатора. Тут, возможно, есть еще второй полезный механизм —
std::initializer_list
. - Если пункт 2. по каким-то причинам не применим, можно в качестве инициализатора передавать лямбду, которая замкнет все параметры инициализатора на себя.
- Деинициализатор имеет единственный параметр — тип ресурса, но в случае необходимости также может быть лямбдой, замыкая на себя дополнительные параметры деинициализации.
- Это намного проще в реализации чем
std::shared_ptr(T* ptr, deleter d)
.
Недостатки?
Иногда все же эффективней написать полноценный врапер ресурса.
Нужно больше примеров? Их есть у меня:
Создание AVFormatContext контекста:
ha::scoped_resource<ffmpeg::AVFormatContext*> formatctx (ffmpeg::avformat_alloc_context, ffmpeg::avformat_free_context);
Это есть аналог следующего:
std::shared_ptr<ffmpeg::AVFormatContext> formatctx = std::shared_ptr<ffmpeg::AVFormatContext> (ffmpeg::avformat_alloc_context(), ffmpeg::avformat_free_context);
Или вот еще, тут применяется составной деинициализатор:
ha::scoped_resource<ffmpeg::AVCodecContext*> codecctx( ffmpeg::avcodec_alloc_context, [](ffmpeg::AVCodecContext* c) { ffmpeg::avcodec_close(c), ffmpeg::av_free(c); });
А этот пример интересен тем, что происходит захват ресурса, который не нужно освобождать:
ha::scoped_resource<ffmpeg::AVCodec*, ffmpeg::AVCodecID> codec( ffmpeg::avcodec_find_decoder, codecctx->codec_id, [](__attribute__((unused)) ffmpeg::AVCodec* c) { });
И наконец самый простой oneliner:
ha::scoped_resource<ffmpeg::AVFrame*> frame(ffmpeg::avcodec_alloc_frame, ffmpeg::av_free);
Который аналог следующего:
std::shared_ptr<ffmpeg::AVFrame> frame = std::shared_ptr<ffmpeg::AVFrame>(ffmpeg::avcodec_alloc_frame(), ffmpeg::av_free);
Но неужели это все про naked plain-C ресурсы? А где же примеры с годным С++?
А вот:
ha::mutex mutex; ha::scoped_resource<ha::mutex*, ha::mutex*> scoped_lock( [](ha::mutex* m) -> ha::mutex* { return m->lock(), m; }, &mutex, [](ha::mutex* m) -> void { m->unlock(); } );
Хорошо, но где же реализация?
Реализация класса scoped_resource настолько проста и элегантна, что даже чем-то напомнила мне идею Y-combinator‘a.
То есть возможно с легкостью реализовать что-то подобное, просто начав с декларации конструктора scoped_resource::scoped_resource(initializer_t, finalizer_t);
и затем наращивать variadic-часть для параметров.
Вот как-то так.
ссылка на оригинал статьи http://habrahabr.ru/post/172817/
Добавить комментарий