相关主题
std::unique_ptr, deleters and the Win32 API
要将Win32句柄用作RAII,我可以使用以下行
std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&CloseHandle)> m_mutex(CreateMutex(NULL, FALSE, NULL), &::CloseHandle);
对我而言,这是一个干净的单行,并且完全符合我的要求。
说到SOCKET,它不会用同一行编译,因为SOCKET不能是nullptr。
要使其发挥作用,我需要做的是:
struct SocketDeleter
{
typedef SOCKET pointer;
void operator()(SOCKET h)
{
::closesocket(h);
}
};
// Start listen socket.
std::unique_ptr<SOCKET, SocketDeleter> sock(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));
我在这个实现中不喜欢的是我想要使用的任何不同类型的资源,我需要复制/粘贴相同的代码才能更改关闭函数。
我可以使用宏,但这真的很丑,不能使用两次
#define RAII_UNIQUE_RESOURCE(varName, classType, init, closure) \
struct deleterMacro \
{ \
typedef classType pointer; \
void operator()(classType h) \
{ \
closure(h); \
} \
}; \
std::unique_ptr<classType, deleterMacro> varName(init);
// Compile, but breaks as soon as 2 sockets defined.
RAII_UNIQUE_RESOURCE(sock, SOCKET, socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP), ::closesocket);
我试图使用模板,但据我所知,我无法将函数指针传递给operator()函数。
template<class T, class methodDeclaration, class pFuncPointer>
struct deleter
{
typedef T pointer;
void operator()(T h)
{
// Is there a way??
methodDeclaration toCall = pFuncPointer;
toCall(h);
}
};
// With a call such as ...
std::unique_ptr<SOCKET, deleter<SOCKET, std::function<decltype(::closesocket)>, ::closesocket>> sock2(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));
答案 0 :(得分:10)
众所周知,RA FILE*
使用std::unique_ptr
:
struct FILEDeleter
{
typedef FILE *pointer;
void operator()(FILE *fp) { fclose(fp); }
};
typedef std::unique_ptr<FILE, FILEDeleter> FilePtr;
FilePtr f(fopen("file.txt", "r"));
唉,POSIX close()
类似于RAII文件描述符的方法是不可能的:
struct FDDeleter
{
typedef int pointer;
void operator()(int fd) { close(fp); }
};
typedef std::unique_ptr<int, FDDeleter> FD;
虽然有些编译器可以正常工作,但它无效,因为fd==0
是一个有效的文件描述符!空值应为-1
。但无论如何,即使它是0
它仍然无效,因为FDDeleter::pointer
应满足 NullablePointer 的要求(总结):
nullptr
相当。nullptr
的值。因此,UniqueHandle
诞生了!
#include <memory>
template <typename T, T TNul = T()>
class UniqueHandle
{
public:
UniqueHandle(std::nullptr_t = nullptr)
:m_id(TNul)
{ }
UniqueHandle(T x)
:m_id(x)
{ }
explicit operator bool() const { return m_id != TNul; }
operator T&() { return m_id; }
operator T() const { return m_id; }
T *operator&() { return &m_id; }
const T *operator&() const { return &m_id; }
friend bool operator == (UniqueHandle a, UniqueHandle b) { return a.m_id == b.m_id; }
friend bool operator != (UniqueHandle a, UniqueHandle b) { return a.m_id != b.m_id; }
friend bool operator == (UniqueHandle a, std::nullptr_t) { return a.m_id == TNul; }
friend bool operator != (UniqueHandle a, std::nullptr_t) { return a.m_id != TNul; }
friend bool operator == (std::nullptr_t, UniqueHandle b) { return TNul == b.m_id; }
friend bool operator != (std::nullptr_t, UniqueHandle b) { return TNul != b.m_id; }
private:
T m_id;
};
它的使用非常简单,最好用一个例子来看:
struct FDDeleter
{
typedef UniqueHandle<int, -1> pointer;
void operator()(pointer p)
{
close(p);
}
};
typedef std::unique_ptr<int, FDDeleter> FD;
FD fd(open("test.txt", O_RDONLY));
如果你真的想要一个单行,你可以采用这种概括:
template <typename T, T TNul = T(), typename RD, RD (*D)(T)>
struct OLDeleter
{
typedef UniqueHandle<T, TNul> pointer;
void operator()(pointer p)
{
D(p);
}
};
然后只有一行:
std::unique_ptr<int, OLDeleter<int, -1, int, close> > FD fd(open("test.txt", O_RDONLY));
问题是你必须添加close()
的返回值作为模板参数,并假设这个函数没有任何有趣的东西阻止它转换为int(*)(int)
(奇怪的调用约定,额外的参数,宏...),这是非常不方便的。
您可以添加函数包装器:
void my_close(int fd) { close(fd); }
但如果你参与其中,你也可以写出整个struct FDDeleter
。
答案 1 :(得分:3)
Kerrek SB在评论中回答说,这正是我想要的!
template <typename T, typename D, D Deleter>
struct stateless_deleter
{
typedef T pointer;
void operator()(T x)
{
Deleter(x);
}
};
std::unique_ptr<SOCKET, stateless_deleter<SOCKET, int(*)(SOCKET), &::closesocket>> listenSocket(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));
答案 2 :(得分:3)
我经常在C ++ 11中使用它:
#include <utility>
namespace{
template<typename F>
struct RAII_Helper{
template<typename InitFunction>
RAII_Helper(InitFunction &&init, F &&exit) : f_(std::forward<F>(exit)), canceled(false){
init();
}
RAII_Helper(F &&f) : f_(f), canceled(false){
}
~RAII_Helper(){
if (!canceled)
f_();
}
void cancel(){
canceled = true;
}
private:
F f_;
bool canceled;
};
}
template<class F>
RAII_Helper<F> RAII_do(F &&f){
return RAII_Helper<F>(std::forward<F>(f));
}
template<class Init, class Exit>
RAII_Helper<Exit> RAII_do(Init &&init, Exit &&exit){
return RAII_Helper<Exit>(std::forward<Init>(init), std::forward<Exit>(exit));
}
用法:
FILE *f = fopen("file", "r");
if (!f)
return "error";
auto filecloser = RAII_do([=]{fclose(f);});
//also makes for good init / exit objects
static auto initExit = RAII_do([]{initializeLibrary();}, []{exitLibrary();});
我喜欢它,因为它适用于任意代码,而不仅仅是指针或句柄。如果从未使用过,也可以省略取消功能。
答案 3 :(得分:2)
最后,我想要另一个Kerrek SB答案。这是STD Unique Resource的提议。
#ifndef UNIQUE_RESOURCE_H_
#define UNIQUE_RESOURCE_H_
#include <type_traits>
// From standard proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf
// Slightly modified to compile on VS2012.
namespace std
{
namespace experimental
{
enum class invoke_it { once, again };
template<typename R,typename D>
class unique_resource_t
{
R resource;
D deleter;
bool execute_on_destruction; // exposition only
unique_resource_t& operator=(unique_resource_t const &);
unique_resource_t(unique_resource_t const &); // no copies!
public:
// construction
explicit unique_resource_t(R && resource, D && deleter, bool shouldrun=true)
: resource(std::move(resource))
, deleter(std::move(deleter))
, execute_on_destruction(shouldrun)
{
}
// move
unique_resource_t(unique_resource_t &&other) /*noexcept*/
:resource(std::move(other.resource))
,deleter(std::move(other.deleter))
,execute_on_destruction(other.execute_on_destruction)
{
other.release();
}
unique_resource_t& operator=(unique_resource_t &&other)
{
this->invoke(invoke_it::once);
deleter=std::move(other.deleter);
resource=std::move(other.resource);
execute_on_destruction=other.execute_on_destruction;
other.release();
return *this;
}
// resource release
~unique_resource_t()
{
this->invoke(invoke_it::once);
}
void invoke(invoke_it const strategy = invoke_it::once)
{
if (execute_on_destruction) {
try {
this->get_deleter()(resource);
} catch(...){}
}
execute_on_destruction = strategy==invoke_it::again;
}
R const & release() /*noexcept*/{
execute_on_destruction = false;
return this->get();
}
void reset(R && newresource) /*noexcept*/ {
invoke(invoke_it::again);
resource = std::move(newresource);
}
// resource access
R const & get() const /*noexcept*/ {
return resource;
}
operator R const &() const /*noexcept*/
{
return resource;
}
R operator->() const /*noexcept*/
{
return resource;
}
// Couldn't make this function compile on VS2012
// std::add_lvalue_reference<std::remove_pointer<R>::type>::type operator*() const
// {
// return * resource;
// }
// deleter access
const D & get_deleter() const /*noexcept*/
{
return deleter;
}
};
//factories
template<typename R,typename D>
unique_resource_t<R,D> unique_resource( R && r,D t) /*noexcept*/
{
return unique_resource_t<R,D>(std::move(r), std::move(t),true);
}
template<typename R,typename D>
unique_resource_t<R,D>
unique_resource_checked(R r, R invalid, D t ) /*noexcept*/ {
bool shouldrun = (r != invalid);
return unique_resource_t<R,D>(std::move(r), std::move(t), shouldrun);
}
}
}
#endif /* UNIQUE RESOURCE H */
用法
auto listenSocket = std::experimental::unique_resource_checked(socket(AF_INET,SOCK_STREAM,IPPROTO_TCP), INVALID_SOCKET, closesocket);
希望这很快就会成为标准!
答案 4 :(得分:1)
稍微不同的方法(在RAII习语的前提下)是使用提升范围退出。
示例:
#include <boost/scope_exit.hpp>
#include <cstdlib>
#include <cstdio>
#include <cassert>
int main()
{
std::FILE* f = std::fopen("example_file.txt", "w");
assert(f);
BOOST_SCOPE_EXIT(f) {
// Whatever happened in scope, this code will be
// executed and file will be correctly closed.
std::fclose(f);
} BOOST_SCOPE_EXIT_END
// Some code that may throw or return.
// ...
}
使用this功能,您实际上可以指定独立的&#34; RAII析构函数&#34;。使用它可以使您的代码更清晰,更清晰,并避免在所有功能更容易合并(或已经是)在课程内部时使用。析构函数。
该语言似乎会添加soon 其他RAII功能。如果可用,您将能够使用类似this的scoped_resource
之类的内容(我将引用该链接以完全了解您的要求)
答案 5 :(得分:1)
这是一个可能的解决方案,以NetCDF C API为例,它具有简单的整体作为句柄:
retval = nc_open(..., &id);
... // validate
std::unique_ptr<int, void(*)(int*)> always_be_closing(&id, [](int* p){nc_close(*p);});
当然,如有必要,您可以检查lambda中的值。
... [](int* p){ if(p) nc_close(*p); }
并且typedef使它更好一些:
typedef std::unique_ptr<int, void(*)(int*)> nc_closer;
...
nc_closer abc(&id, [](int* p){nc_close(*p);});
你可能想要一个减少重复的功能:
static void nc_close_p(int* p) { nc_close(*p); }
...
nc_closer abc(&id, &nc_close_p);
或:
auto abc = auto_nc_close(&id, &nc_close_p);
由于unique_ptr
实现了operator bool
,您也可以将其用作块范围,例如C#中的using
:
if (auto abc = auto_nc_close(&id, &nc_close_p))
{
...
}