在c++ 03中,当你要在一个类中包装一堆C函数来创建一个'auto object'时,你必须根据它封装的函数的类型自定义对象。例如,要包装一个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
或unique_ptr
具有类似的结构,但是构造函数将是由开发人员编写的创建者和deleter类创建资源的构造函数。我有一种感觉,std::bind可能在这方面发挥了作用,但我从未使用过它。
这是一个尝试:
#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)...) ); }
};
一个比基本功能更需要的操作是将句柄"null"的能力,允许无效句柄存在,并允许移动句柄。Zeroer
是我的默认函数对象"空"一个句柄:
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 closedn";
return true;
}
}
一旦你有了你的打开/关闭函数或函数对象,下面是你如何创建FileHandle
的类型:
typedef RAII_handle< HANDLE, Functor< HANDLE(*)( char const* ), CreateFile >, Functor< bool(*)(HANDLE), CloseFile > > FileHandle;
您可以通过简单地创建一个转发到固定函数名而不是固定函数指针的函数对象来支持整个重载集。基本上取上面的Functor
,去掉template
的签名和指针,用函数名的实际用法代替func
的用法。
突然间,你的函数对象代表的不是调用一个函数,而是调用整个重载集。
更花哨的工作甚至可以支持多个函数,允许一个函数对象根据传入的参数支持调用CreateFile
或CreateFileEx
。
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()(...)
只是将0赋值给T
,这适用于许多句柄类型。
它支持移动语义,允许您从函数返回这些,但我没有包括Copier
参数(我预计任何可以复制的RAII对象都需要)。