Какой класс оболочки в С++ следует использовать для автоматического управления ресурсами?
Я любитель С++. Я пишу некоторый код Win32 API, и есть ручки и странно спланированные объекты. Поэтому мне было интересно - есть ли какой-нибудь класс-оболочка, который облегчит управление ресурсами?
Например, когда я хочу загрузить некоторые данные, я открываю файл с CreateFile()
и получаю HANDLE
. Когда я покончу с этим, я должен называть CloseHandle()
на нем. Но для любой достаточно сложной функции загрузки будут десятки возможных точек выхода, не говоря уже об исключениях.
Итак, было бы здорово, если бы я мог обернуть дескриптор в какой-то класс-оболочку, который автоматически вызывет CloseHandle()
после того, как выполнение выйдет из области. Еще лучше - он мог бы подсчитать количество ссылок, чтобы я мог передавать его в других функциях и из них, и он освобождает ресурс только тогда, когда последняя ссылка оставила область видимости.
Концепция проста - но есть ли что-то подобное в стандартной библиотеке? Кстати, я использую Visual Studio 2008, и я не хочу прикладывать стороннюю структуру, такую как Boost или что-то в этом роде.
Ответы
Ответ 1
Напишите свое. Это всего лишь несколько строк кода. Это просто такая простая задача, что не стоит предлагать универсальную многоразовую версию.
struct FileWrapper {
FileWrapper(...) : h(CreateFile(...)) {}
~FileWrapper() { CloseHandle(h); }
private:
HANDLE h;
};
Подумайте о том, что должна делать общая версия: она должна быть параметризуемой, поэтому вы можете указать любую пару функций и любое количество аргументов. Просто создание экземпляра такого объекта, скорее всего, займет столько строк кода, сколько указано выше.
Конечно, С++ 0x может немного рассказать о балансе с добавлением лямбда-выражений. Два лямбда-выражения могут быть легко переданы в общий класс-оболочку, поэтому после поддержки С++ 0x мы можем увидеть, что такой общий класс RAII добавлен в Boost или что-то в этом роде.
Но на данный момент проще просто сворачивать свои собственные, когда вам это нужно.
Что касается добавления подсчета ссылок, я бы посоветовал это сделать. Счетный счет дорог (внезапно ваш дескриптор должен быть динамически распределен, а контрольные счетчики должны поддерживаться на каждом задании), и очень сложно получить право. Это область, которая просто разрывается в тонких условиях гонки в многопоточной среде.
Если вам нужен подсчет ссылок, просто сделайте что-то вроде boost::shared_ptr<FileWrapper>
: оберните собственные классы ad-hoc RAII в shared_ptr
.
Ответ 2
По существу, fstream
- хорошая С++-оболочка для дескрипторов файлов. Это часть стандарта, которая означает, что она переносима, хорошо протестирована и расширяема объектно-ориентированным способом. Для файловых ресурсов это отличная концепция.
Однако fstream
работает только для файлов, а не для общих ручек, то есть потоков, процессов, объектов синхронизации, файлов с отображением памяти и т.д.
Ответ 3
Здесь один основан на коде EnsureCleanup из "Windows через C/С++":
http://www.codeproject.com/KB/cpp/template2003.aspx
Ответ 4
MFC имеет некоторые подходящие примитивы (смотрите CFile, например, но не стандартную библиотеку.
Ответ 5
Visual С++ 2008 поддерживает TR1 через Feature Pack, а TR1 включает shared_ptr. Я бы использовал это - это очень мощный класс интеллектуальных указателей и может быть обобщен для того, чтобы выполнять тип управления ресурсами, о котором вы просите.
TR1 фактически является расширением стандарта. Я считаю, что он по-прежнему официально "предстандарт", но эффективно вы можете считать его заблокированным.
Ответ 6
Я не думаю, что в стандартной библиотеке есть что-то, и я также сомневаюсь, что общие указатели (как в boost) могут использоваться (так как они ожидали бы указателя на HANDLE, а не HANDLE).
Не стоит записывать их самостоятельно, следуя scope guard idiom (и используя шаблоны/указатели функций и т.д., если вы так выбираете).
Ответ 7
template <typename Traits>
class unique_handle
{
using pointer = typename Traits::pointer;
pointer m_value;
auto close() throw() -> void
{
if (*this)
{
Traits::close(m_value);
}
}
public:
unique_handle(unique_handle const &) = delete;
auto operator=(unique_handle const &)->unique_handle & = delete;
explicit unique_handle(pointer value = Traits::invalid()) throw() :
m_value{ value }
{
}
unique_handle(unique_handle && other) throw() :
m_value{ other.release() }
{
}
auto operator=(unique_handle && other) throw() -> unique_handle &
{
if (this != &other)
{
reset(other.release());
}
return *this;
}
~unique_handle() throw()
{
close();
}
explicit operator bool() const throw()
{
return m_value != Traits::invalid();
}
auto get() const throw() -> pointer
{
return m_value;
}
auto get_address_of() throw() -> pointer *
{
ASSERT(!*this);
return &m_value;
}
auto release() throw() -> pointer
{
auto value = m_value;
m_value = Traits::invalid();
return value;
}
auto reset(pointer value = Traits::invalid()) throw() -> bool
{
if (m_value != value)
{
close();
m_value = value;
}
return static_cast<bool>(*this);
}
auto swap(unique_handle<Traits> & other) throw() -> void
{
std::swap(m_value, other.m_value);
}
};
template <typename Traits>
auto swap(unique_handle<Traits> & left,
unique_handle<Traits> & right) throw() -> void
{
left.swap(right);
}
template <typename Traits>
auto operator==(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() == right.get();
}
template <typename Traits>
auto operator!=(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() != right.get();
}
template <typename Traits>
auto operator<(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() < right.get();
}
template <typename Traits>
auto operator>=(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() >= right.get();
}
template <typename Traits>
auto operator>(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() > right.get();
}
template <typename Traits>
auto operator<=(unique_handle<Traits> const & left,
unique_handle<Traits> const & right) throw() -> bool
{
return left.get() <= right.get();
}
struct null_handle_traits
{
using pointer = HANDLE;
static auto invalid() throw() -> pointer
{
return nullptr;
}
static auto close(pointer value) throw() -> void
{
VERIFY(CloseHandle(value));
}
};
struct invalid_handle_traits
{
using pointer = HANDLE;
static auto invalid() throw() -> pointer
{
return INVALID_HANDLE_VALUE;
}
static auto close(pointer value) throw() -> void
{
VERIFY(CloseHandle(value));
}
};
using null_handle = unique_handle<null_handle_traits>;
using invalid_handle = unique_handle<invalid_handle_traits>;
Ответ 8
Эти обертки называются ATL.
Если ваш дескриптор - событие или подобное, используйте класс CHandle.
Если ваш дескриптор является файлом, используйте производный от CAtlFile, он оборачивает такие API, как CreateFile и ReadFile.
В ATL есть и другие полезные оболочки, CAtlFileMapping<T>
- это оболочка RAII для файлов, отображаемых в память, CPath
оболочки API-интерфейсов shell32 для обработки путей и т.д.
ATL - это большая библиотека, но вещи низкого уровня, такие как файлы, строки и коллекции, изолированы. Вы можете использовать их во всех приложениях Win32. это только заголовок, вам не нужно связываться с чем-либо или распространять дополнительные библиотеки DLL, такие как MFC или CRT, код компилируется в вызовы WinAPI и просто работает.
Я не помню, чтобы они были отделены от MFC в VS2003 или 2005, то есть в Visual Studio 2008 они точно есть. Однако есть одно предостережение: если вы используете бесплатную версию VS, она должна быть 2015 года или новее.