Однострочный для RAII по не указателю?
Связанная тема
std:: unique_ptr, удалители и API Win32
Чтобы использовать ручку 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));
Ответы
Ответ 1
Наконец, я хочу с другим ответом Kerrek SB. Это предложение для уникального ресурса STD.
#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);
Надеюсь, что это сделает std достаточно скоро!
Ответ 2
Хорошо известен пример для RAII a 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
.
Ответ 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));
Ответ 4
Я часто использую это в С++ 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();});
Мне нравится, потому что он работает для произвольного кода, а не только указателей или дескрипторов. Также функция отмены может быть опущена, если ее не использовать.
Ответ 5
Немного другой подход (в пределах помещения идиомы 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.
// ...
}
Используя эту функциональность, вы практически определяете автономные действия RAIM-деструктора. Используйте, где он делает ваш код более четким и чистым, и избегайте, когда вся функциональность будет легче включаться (или уже есть) внутри деструктора класса.
Похоже, что скоро добавлена дополнительная функциональность RAII. Когда доступно, вы сможете использовать что-то вроде scoped_resource
, которое выглядит как this (я бы назвал эта ссылка для полноценной реализации того, что вы просите)
Ответ 6
Здесь одно возможное решение, использующее в качестве примера API NetCDF C, который имеет простые ints как дескрипторы:
retval = nc_open(..., &id);
... // validate
std::unique_ptr<int, void(*)(int*)> always_be_closing(&id, [](int* p){nc_close(*p);});
Конечно, вы можете проверить значение в лямбда, если это необходимо.
... [](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
, вы также можете использовать его как область блока, например using
в С#:
if (auto abc = auto_nc_close(&id, &nc_close_p))
{
...
}