我应该使用 C++ 中的哪个包装类来进行自动化资源管理?

Posted

技术标签:

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

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

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

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

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

【问题讨论】:

【参考方案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>;

【讨论】:

最好在你的回答中添加一些描述。

以上是关于我应该使用 C++ 中的哪个包装类来进行自动化资源管理?的主要内容,如果未能解决你的问题,请参考以下文章

我应该使用哪个 C++ GUI 库

我应该在 Windows 上为 C++ 使用哪个 IDE? [关闭]

Facebook SDK 包装器的自动化测试

我应该使用 ExitThread() 还是从 C++ 中的线程返回

围绕 JSON 数据包装一个 python 类,哪个更好?

在堆栈上分配不完整的类型