1

在 C++03 中,当您将一堆 C 函数包装在一个类中以创建“自动对象”时,您必须将对象自定义为它封装的函数类型。例如,要包装 Windows 文件 HANDLE,您需要在析构函数中调用CloseHandle() ,在构造函数中调用CreateFile()。构造函数需要模仿 CreateFile() 函数的函数签名,没有文件 HANDLE 变量(因为它正在被管理)。

无论如何,我想知道的是,是否可以使用 C++11 的新特性来创建一个通用类,该类可用于包装任何类型的资源,只提供创建和删除的实现?

我预见到的一个问题是创建函数,例如上面提到的 CreateFile(),可以采用任意数量的参数。有没有办法自动生成一个模仿函数签名的模板化构造函数?想到可变参数,但我还没有使用它们。

有没有人试过写这样的东西?

编辑:一些代码来帮助说明(伪):

template<typename Res, FunctionPtrToCreatorFunc Func, typename... Arguments>
class creator
{
public:
  operator()(Res &r, Arguments... Args)
  {
    Func(r, /*use args?*/ Args); // Allocate resource, ie. CreateFile(r, args)
  }
};

template<typename Res, FunctionPtrToDeleterFunc Func>
class deleter
{
  operator()(Res &r)
  {
    Func(r); // delete the resource, ie. CloseHandle(r)
  }
};

然后这将是我的超级自动对象的实现:

template<typename Res, typename Creator, typename Deleter>
class auto_obj
{
public:

  auto_obj(/*somehow copy Args from Creator class?*/)
  {
    Creator(_res, /*args?*/);
  }

  ~auto_obj()
  {
    deleter(_res);
  }

  Res _res;
};

shared_ptr是的,这与or具有类似的结构unique_ptr,但构造函数将是由开发人员编写的创建者和删除器类创建资源的构造函数。我有一种感觉 std::bind 可能在其中发挥作用,但我从未使用过它。

4

1 回答 1

1

这是一个尝试:

#include <utility>
#include <type_traits>
#include <cstddef>

包装函数的更友好的方式。我将签名样板移至 this template,而不是弄乱下面的实际 RAII 类。这也允许在下面的 RAII 类中使用完整的函数对象以及函数:

template< typename FuncSig, FuncSig func >
struct Functor {
  template<typename... Args>
  auto operator()(Args&&... args) const
  -> decltype( func(std::forward<Args>(args)...) )
    { return ( func(std::forward<Args>(args)...) ); }
};

除了基本功能之外,需要的一项操作是将句柄“空”,允许存在无效句柄,并允许移动句柄。 Zeroer是我的“null”句柄的默认函数对象:

struct Zeroer {
  template<typename T>
  void operator()( T& t ) const {
    t = 0;
  }
};

RAII_handle她自己。您将创建和销毁签名打包到其中,并将构造转发到基础数据。 .close()让您RAII_handle提前关闭,这是实践中的常见要求。operator*您通过or访问底层数据operator->,虽然这使它看起来像指针,RAII_handle但不遵守指针语义。它是一种只能移动的类型。

template< typename T, typename Creator, typename Destroyer, typename Nuller=Zeroer >
struct RAII_handle {
  RAII_handle( std::nullptr_t ):
    data()
  {
    Nuller()(data);
  }
  RAII_handle( RAII_handle const& ) = delete;
  RAII_handle( RAII_handle && o ):data(std::move(o.data)) {
    Nuller()(o.data);
  }
  RAII_handle& operator=( RAII_handle const& ) = delete;
  RAII_handle& operator=( RAII_handle && o ) {
    data = std::move(o.data);
    Nuller()(o.data);
    return *this;
  }
  template<typename... Args>
  RAII_handle( Args&&... args ):
    data( Creator()(std::forward<Args>(args)...) )
  {}
  auto close()->decltype( Destroyer()(std::declval<T&>()) ) {
    auto retval = Destroyer()(data);
    Nuller()(data);
    return retval;
  }
  ~RAII_handle() {
    close();
  }
  T& get() { return data; }
  T const& get() const { return data; }

  T& operator*() { return get(); }
  T const& operator*() const { return get(); }

  T* operator->() { return &get(); }
  T const* operator->() const { return &get(); }
private:
  T data;
};

现在,一些测试代码。我的文件句柄将是unsigned char,并且打开/关闭将简单地测试事情是否正常。

#include <iostream>
typedef unsigned char HANDLE;
HANDLE CreateFile( char const* name ) {
  std::cout << name << "\n";
  return 7;
}
bool CloseFile( HANDLE h ) {
  if (h) {
    --h;
    std::cout << (int)h << "\n";
    return true;
  } else {
    std::cout << "already closed\n";
    return true;
  }
}

一旦你有了你的打开/关闭函数或函数对象,下面是你如何创建类型的FileHandle

typedef RAII_handle< HANDLE, Functor< HANDLE(*)( char const* ), CreateFile >, Functor< bool(*)(HANDLE), CloseFile > > FileHandle;

您可以通过简单地创建一个转发到固定函数名称而不是固定函数指针的函数对象来支持整个重载集。基本上采取Functor上面,删除template签名和指针,并用func你的函数名的实际使用替换使用。

突然间,您的函数对象代表不调用一个函数,而是调用整个重载集。

更高级的工作甚至可以支持多个函数,允许一个函数对象支持调用其中一个CreateFileCreateFileEx取决于传入的参数。

这是我们的简单测试代码:

int main() {
  FileHandle bob("hello.txt");
  HANDLE value = *bob; // get the HANDLE out of the FileHandle
  bob.close(); // optional, to close early
}

要求:你CloseFile必须接受Nuller()(std::declval<T&>())并且不做坏事。默认值Nuller()(...)只是将零分配给 your T,这适用于许多句柄类型。

它支持移动语义,允许您从函数中返回这些,但我没有包含Copier参数(我希望任何可以复制的 RAII 对象都需要该参数)。

代码略有不同的实时示例。

于 2013-06-06T12:07:13.367 回答