在没有代码重复的情况下在自动对象中包装C函数

时间:2013-06-03 13:49:42

标签: c++ c++11 variadic-templates resource-management

在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_ptrunique_ptr类似的结构,但构造函数将是由开发人员编写的创建者和删除者类创建资源的构造函数。我有一种感觉,std :: bind可能在这方面发挥作用,但我从未使用它。

1 个答案:

答案 0 :(得分:1)

这是一个刺痛:

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

一种更友好的方式来结束一个功能。我将签名样板移动到此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*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的使用替换为实际使用的函数名称。

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

Fancier工作甚至可以支持多个函数,允许一个函数对象支持调用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()(...)只为您的T指定零,这适用于多种句柄类型。

它支持移动语义,允许你从函数返回它们,但是我没有包含Copier参数(我希望任何可以复制的RAII对象都需要它)。 / p>

Live example代码略有不同。