Какой класс-оболочку в C ++ я должен использовать для автоматизированного управления ресурсами?
-
19-09-2019 - |
Вопрос
Я любитель C ++.Я пишу некоторый код Win32 API, и там в изобилии есть дескрипторы и странно композиционно распределенные объекты.Итак, мне было интересно - есть ли какой-нибудь класс-оболочка, который упростил бы управление ресурсами?
Например, когда я хочу загрузить какие-то данные, я открываю файл с CreateFile()
и получить HANDLE
.Когда я закончу с этим, мне следует позвонить CloseHandle()
на нем.Но для любой достаточно сложной функции загрузки будут существовать десятки возможных точек выхода, не говоря уже об исключениях.
Поэтому было бы здорово, если бы я мог обернуть дескриптор в какой-нибудь класс-оболочку, который автоматически вызывал бы CloseHandle()
как только выполнение вышло за рамки.Еще лучше - он мог бы выполнять некоторый подсчет ссылок, чтобы я мог передавать его в другие функции и из них, и он освобождал бы ресурс только тогда, когда последняя ссылка покидала область видимости.
Концепция проста - но есть ли что-то подобное в стандартной библиотеке?Кстати, я использую Visual Studio 2008, и я не хочу подключать сторонний фреймворк, такой как Boost или что-то в этом роде.
Решение
Напишите свой собственный.Это всего лишь несколько строк кода.Это просто такая простая задача, что это не оно того стоило предоставить универсальную версию для повторного использования.
struct FileWrapper {
FileWrapper(...) : h(CreateFile(...)) {}
~FileWrapper() { CloseHandle(h); }
private:
HANDLE h;
};
Подумайте о том, что должна была бы делать универсальная версия:Он должен быть параметризуемым, чтобы вы могли указать Любой пара функций, и Любой количество аргументов к ним.Простое создание экземпляра такого объекта, скорее всего, заняло бы столько строк кода, сколько указано в приведенном выше определении класса.
Конечно, C ++ 0x может несколько изменить баланс с добавлением лямбда-выражений.Два лямбда-выражения могут быть легко переданы в универсальный класс-оболочку, поэтому, как только появится поддержка C ++ 0x, мы мог бы посмотрите на такой универсальный класс RAII, добавленный в Boost или что-то в этом роде.
Но на данный момент проще просто свернуть свой собственный, когда вам это нужно.
Что касается добавления подсчета ссылок, я бы не советовал этого делать.Подсчет ссылок обходится дорого (внезапно ваш дескриптор должен быть динамически распределен, а счетчики ссылок должны поддерживаться при каждом назначении), и его очень сложно выполнить правильно.Это область, просто изобилующая тонкими условиями гонки в многопоточной среде.
Если вы делай нужен подсчет ссылок, просто сделайте что-то вроде boost::shared_ptr<FileWrapper>
:оберните ваши пользовательские ad-hoc классы RAII в shared_ptr
.
Другие советы
По существу, fstream
это хорошая оболочка C ++ для дескрипторов файлов.Это часть стандарта, что означает, что он переносим, хорошо протестирован и расширяем объектно-ориентированным образом.Для файловых ресурсов это отличная концепция.
Однако, fstream
работает только для файлов, а не для общих дескрипторов, т.е.потоки, процессы, объекты синхронизации, файлы с отображением в память и т.д.
Эти обертки называются ATL.
Если ваш дескриптор является событием или чем-то подобным, используйте Свеча класс.
Если ваш дескриптор является файлом, используйте производный от 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 года выпуска или новее.
Вот один из них, основанный на коде EnsureCleanup из "Windows через C / C ++":http://www.codeproject.com/KB/cpp/template2003.aspx
В MFC есть несколько подходящих примитивов (посмотрите на Файл CFile например), но не стандартная библиотека.
Visual C ++ 2008 поддерживает TR1 через пакет функциональных возможностей, а TR1 включает shared_ptr.Я бы использовал это - это очень мощный класс интеллектуальных указателей, и его можно обобщить для выполнения того типа управления ресурсами, о котором вы просите.
TR1 фактически является расширением Стандарта.Я полагаю, что официально он все еще "предстандартный", но фактически вы можете считать его заблокированным.
Я не думаю, что в стандартной библиотеке что-то есть, и я также сомневаюсь, что можно использовать общие указатели (как в boost) (поскольку они ожидали бы, что указатель будет обрабатываться, а не HANDLE ).
Написать его самостоятельно не должно составить труда, следуя защита прицела идиома (и использование шаблонов / указателей на функции и т.д., если вы того пожелаете).
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>;