65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的基于模板的智能句柄类

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.56/5 (5投票s)

2004年4月5日

3分钟阅读

viewsIcon

48991

downloadIcon

345

一个模板类,可以“智能地”管理 Win32 对象的生命周期,其中包含一个内存映射文件包装器的示例。

引言

在 Win32 编程中,API 调用常常返回一个 HANDLE 或其他不透明的句柄,之后必须通过另一个 API 调用来释放或关闭。管理这些释放操作可能会很复杂,特别是在 API 调用失败需要清理,或者代码中的其他地方发生异常时。

AutoClose 是一个模板类,用于管理使用后需要释放的 HANDLE 和类似的 Windows 对象。源代码演示了该模板的两种用法:一个简单的目录列表应用程序,以及一个使用 AutoClose 构建的内存映射文件类来打开并将指定文件复制到控制台的应用程序。

背景

“资源获取即初始化”(Resource Acquisition Is Initialization)似乎是描述将类实例的生命周期与外部对象生命周期绑定的概念的统称。最常见的实现方式是“智能指针”,用于管理动态分配的内存和对象。表面上看,智能指针的设计和使用非常简单,但实际上,要将其设计得尽善尽美非常困难,用户必须关注复制、引用计数、函数传值等语义问题。标准 C++ 库包含一个有限但非常有用的智能指针类型:std::auto_ptr

class Bar
{
    ...
} ;

void Foo ()
{
    std::auto_ptr<Bar> p ( new Bar ) ;
    // use Bar as necessary, return whenever,
    // Bar will get deleted on return from Foo
}

AutoClose 的设计用途类似于 std::auto_ptr,可用作函数内的局部变量或类的成员。它本身并不是一个智能指针,例如它不提供 operator->。CodeProject 上还有其他几篇实现类似功能的文章,但我认为这个实现更简单,尽管它在 C++ 模板方面的探索性稍显不足。其关键特性是可以直接在模板定义中写入要在“释放”时调用的函数名称。

使用代码

AutoClose 的定义如下:

template <typename T, BOOL ( WINAPI * F)( T ), T Invalid = 0> class AutoClose {}

第一个模板参数 'T' 是要保存的对象类型,第二个 'F' 是要调用的 API 函数,用于释放对象,该函数假定接受一个 'T' 类型的参数并返回 BOOL,第三个参数是将被视为“无效”的对象值。

典型用法可能是:

WIN32_FIND_DATA fd ;
AutoClose<HANDLE, ::FindClose, 
   INVALID_HANDLE_VALUE> hFD ( ::FindFirstFile ( "*.*", &fd )) ;
if ( hFD )
{
    while ( ::FindNextFile ( hFD, &fd ))
    {
        ...
    }
}

或者也许:

class HasAnEvent
{
private :
    AutoClose<HANDLE, ::CloseHandle> hEvent_ ;
public :
    HasAnEvent ()
    {
        hEvent_ = ::CreateEvent ( 0, FALSE, FALSE, 0 ) ;
    }
    HANDLE Event ()
    {
        return hEvent_ ;
    }
} ;

这是 AutoClose 类的全部代码:

template<typename T, BOOL ( WINAPI * F)( T ), T Invalid = 0> class AutoClose
{
private :
    T   t_ ;
    void Dispose ()
    {
        if ( t_ != Invalid )
            F ( t_ ) ;
    }
public :
    AutoClose () : t_ ( Invalid )
    {
    }
    explicit AutoClose ( T t ) : t_ ( t )
    {
    }
    ~AutoClose ()
    {
        Dispose () ;
    }
    AutoClose<T, F, Invalid>& operator= ( T t )
    {
        if ( t != t_ )
        {
            Dispose () ;
            t_ = t ;
        }
        return *this ;
    }
    T Detach ()
    {
        T t = t_ ;
        t_ = Invalid ;
        return t ;
    }
    operator T () const
    {
        return t_ ;
    }
    operator bool () const
    {
        return t_ != Invalid ;
    }
    bool operator! () const
    {
        return t_ == Invalid ;
    }
private :
    AutoClose ( AutoClose<T, F, Invalid> const& ) ;
    AutoClose<T, F, Invalid>& 
      operator= ( AutoClose<T, F, Invalid> const& ) ;
} ;

接受 T 参数的构造函数被声明为 explicit,以防止类似这样的代码编译:

int main ( int argc, char ** argv )
{
    AutoClose<HANDLE, ::CloseHandle> Handle = argv ;
    ...
}

Win32 的 HANDLE 类型本质上是一个 void*,任何指针都可以转换为 void*。通过将构造函数声明为 explicit,类型必须完全匹配,上述代码将无法编译,从而捕获潜在的编码错误。不幸的是,这也禁止了那种风格的构造/赋值。

接受 AutoClose 类型参数的复制构造函数和 operator= 被声明为私有且未实现。这是为了避免出现两个 AutoClose 对象同时引用同一个句柄但又没有复制它,从而导致“关闭”次数超过必需的情况。

Detach 允许将托管的句柄从 AutoClose 对象的控制中移除。

HANDLE GetFile ( LPCTSTR sName )
{
    AutoClose<HANDLE, ::CloseHandle, 
       INVALID_HANDLE_VALUE> hFile ( ::CreateFile ( sName,... )) ;
    if ( hFile )
    {
        if ( !ValidateFileHeader ( hFile ))
        {
            return INVALID_HANDLE_VALUE ;
        }
        if ( !ValidateFileContents ( hFile ))
        {
            return INVALID_HANDLE_VALUE ;
        }
    }
    return hFile.Detach () ;
}

operator booloperator! 允许直接测试 AutoClose 对象,以判断其包含的 T 是否为一个有效的句柄。我怀疑这可能存在一些缺点,但希望它们能在编译时显现……

构建说明

我仅在 Windows XP 上使用 Visual C++ 7.1 构建和测试了此类和示例程序。我预计它们可以在早期版本上构建,但我尚未测试或提供解决方案文件。

参考文献

相关的 CodeProject 文章

许可证

您可以用此类和示例代码做任何事情,但不能移除版权或出售源代码。

历史

首次发布 - 2004 年 4 月 5 日

一个简单的基于模板的智能句柄类 - CodeProject - 代码之家
© . All rights reserved.