【问题标题】:What wrapper class in C++ should I use for automated resource management?我应该使用 C++ 中的哪个包装类来进行自动化资源管理?
【发布时间】:2011-01-26 20:32:12
【问题描述】:

我是 C++ 爱好者。我正在编写一些 Win32 API 代码,并且有很多句柄和奇怪的复合分配对象。所以我想知道 - 是否有一些包装类可以使资源管理更容易?

例如,当我想加载一些数据时,我用CreateFile() 打开一个文件并得到一个HANDLE。当我完成它时,我应该打电话给CloseHandle()。但是对于任何相当复杂的加载函数,都会有几十个可能的退出点,更不用说例外了。

因此,如果我可以将句柄包装在某种包装类中,一旦执行离开范围,它将自动调用CloseHandle()。更好的是——它可以做一些引用计数,这样我就可以将它传入和传出其他函数,并且只有在最后一个引用离开作用域时才会释放资源。

这个概念很简单——但标准库中有类似的东西吗?顺便说一句,我使用的是 Visual Studio 2008,我不想附加 Boost 之类的第 3 方框架。

【问题讨论】:

    标签: c++ raii resource-management


    【解决方案1】:

    自己写。这只是几行代码。这只是一项简单的任务,值得提供一个通用的可重用版本。

    struct FileWrapper {
      FileWrapper(...) : h(CreateFile(...)) {}
      ~FileWrapper() { CloseHandle(h); }
    
    private:
      HANDLE h;
    };
    

    想一想通用版本必须做什么:它必须是可参数化的,因此您可以指定 任何 对函数,以及 任何 个参数他们。仅仅实例化这样一个对象可能需要与上述类定义一样多的代码行。

    当然,C++0x 可能会通过添加 lambda 表达式在某种程度上打破平衡。两个 lambda 表达式可以很容易地传递给一个泛型包装类,所以一旦 C++0x 支持出现,我们可能会看到这样一个泛型 RAII 类添加到 Boost 或其他东西中。

    但目前,在需要时自行推出会更容易。

    至于添加引用计数,我建议不要这样做。引用计数很昂贵(突然你的句柄必须动态分配,并且每次分配都必须维护引用计数器),而且很难做到正确。这是一个在线程环境中充满微妙竞争条件的区域。

    如果您确实需要引用计数,只需执行 boost::shared_ptr<FileWrapper> 之类的操作:将您的自定义 ad-hoc RAII 类包装在 shared_ptr 中。

    【讨论】:

    • 代码很糟糕,因为结构可以被复制。看en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
    • @Kerido,也许,也许不是。这取决于您要包装的资源的语义。我认为给 jalf 怀疑的好处并假设发布的代码只是一个简单的说明性示例是公平的。
    • @Kerido:所以...添加两行使复制构造函数和赋值运算符private 和未定义?
    • 是的,如果您想要一个强大的解决方案,绝对应该防止复制。我省略了它以展示一个简短而简单的实现(如果您不尝试变得聪明并复制它就足够了)。防止复制的一种简单方法是从 boost::noncopyable 继承,但是可以,否则将复制 ctor 和赋值运算符设为私有。但正如克里斯托所说,这只是为了说明问题。为简洁起见,我故意省略了复制构造函数。
    【解决方案2】:

    本质上,fstream 是一个很好的文件句柄 C++ 包装器。它是标准的一部分,这意味着它是可移植的、经过良好测试的,并且可以以面向对象的方式进行扩展。对于文件资源来说,这是一个很棒的概念。

    但是,fstream 仅适用于文件,不适用于通用句柄,即线程、进程、同步对象、内存映射文件等。

    【讨论】:

    • 我只使用文件句柄作为一个常见的易于理解的示例。在实践中,事情……更奇怪。
    • 你指的是哪个句柄?
    • SSPI 句柄,如 CredHandle、CtxtHandle 和 SecBufferDesc。最后一个是一个奇怪的结构,它包含一个动态分配的结构数组,其中每个结构都有一个指向动态分配缓冲区的指针。简而言之,它是可变大小缓冲区的可变大小集合。释放功能并不像“删除”那么简单。 :(
    • 刚刚发现这个:drdobbs.com/cpp/184401688。不幸的是,我没有使用 SSPI,所以我不知道该材料是否适合您的情况。
    【解决方案3】:

    这些包装器称为 ATL。

    如果您的句柄是事件或类似的,请使用CHandle 类。

    如果您的句柄是文件,请使用 CAtlFile 派生的文件,它封装了 CreateFile 和 ReadFile 等 API。

    ATL 中还有其他有用的包装器,CAtlFileMapping<T> 是内存映射文件的 RAII 包装器,CPath 包装用于路径处理的 shell32 API,等等。

    ATL 是大型库,但文件、字符串和集合等低级内容是隔离的。您可以在所有 Win32 应用程序中使用它们。只是头文件,你不需要链接任何东西,或者分发额外的 DLL,比如 MFC 或 CRT,代码编译成 WinAPI 调用就可以工作了。

    它们是在 VS2003 或 2005 中从 MFC 中分离出来的,不记得了,即 Visual Studio 2008 肯定有它们。但是有一点需要注意,如果您使用的是 VS 的免费版本,它必须是 2015 年或更新版本。

    【讨论】:

    • 哦,要获得死灵法师徽章吗? ;) 不过,好的答案,请支持我。我什至不记得我为什么问这个。 :D
    • @Vilx- 我已经有 3 个银色的了。顺便说一句,当我发现这个时,我正在从 <stdio.h> 搜索 FILE* 包装器(我不喜欢 <iostream>
    • 我从来没有真正做过认真的 C++ 工作,而且时间越长,我就越意识到这种语言变得多么奇怪。当我查看当今 C++ 代码的示例时,大多数时候我都无法从中得出正面或反面的结论。
    【解决方案4】:

    这是一个基于“Windows via C/C++”中的 EnsureCleanup 代码的代码: http://www.codeproject.com/KB/cpp/template2003.aspx

    【讨论】:

      【解决方案5】:

      MFC 有一些合适的原语(例如查看CFile),但没有标准库。

      【讨论】:

      • 这样的类听起来不是很复杂。也许网络上有一个示例实现,我可以在我的解决方案中复制粘贴?为此,我应该在 Google 中使用哪些关键字?
      • 看这个例子:bbdsoft.com/win32.html“CreateFile CloseHandle wrapper”查询的第一个匹配项。
      • 与使用原始 Win32 编写所有代码相比,CFile 等将大大简化事情。
      • 很好,但我只使用文件句柄作为一个常见的易于理解的示例。实际上,我正在处理需要特殊关闭函数和三重动态分配间接结构的 SSPI 和句柄。稀有物品。
      • 那么用户 jalf 是正确的。正确设置您自己的课程 - 需要一个小时。
      【解决方案6】:

      Visual C++ 2008 通过 Feature Pack 支持 TR1,TR1 包含 shared_ptr。我会使用它——它是一个非常强大的智能指针类,可以泛化为您要求的资源管理类型。

      TR1 实际上是对标准的扩展。我相信它仍然是正式的“准标准”,但实际上你可以认为它已被锁定。

      【讨论】:

      • 请注意,在某些情况下使用shared_ptr 需要您编写自定义删除函数。 (在简单的情况下,您可以将 CloseHandle 函数作为删除器传递。)
      • @celticminstrel - 唯一的问题是(我认为)您需要在调用 ::CloseHandle(...) 之前检查 NULL,所以我认为您无法通过 lambda
      【解决方案7】:

      我认为标准库中没有任何内容,并且我也怀疑是否可以使用共享指针(如在 boost 中)(因为这些指针期望指向 HANDLE,而不是 HANDLE)。

      按照scope guard 成语自己编写应该不难(如果您愿意,还可以使用模板/函数指针等)。

      【讨论】:

        【解决方案8】:
        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>;
        

        【讨论】:

        • 最好在你的回答中添加一些描述。
        猜你喜欢
        • 2017-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-16
        • 1970-01-01
        • 2011-09-30
        相关资源
        最近更新 更多