Какой класс оболочки в С++ следует использовать для автоматического управления ресурсами?

Я любитель С++. Я пишу некоторый код 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 работает только для файлов, а не для общих ручек, то есть потоков, процессов, объектов синхронизации, файлов с отображением памяти и т.д.

Ответ 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 года или новее.