В C ++ 03, когда вы должны были обернуть кучу функций C в классе, чтобы создать «автоматический объект», вы должны были настроить объект в соответствии с типом функций, которые он инкапсулировал. В качестве примера, чтобы обернуть Windows-файл HANDLE, вам нужно было вызвать CloseHandle () в деструкторе и CreateFile () в конструкторе. Конструктор должен имитировать сигнатуру функции функции CreateFile (), без файловой переменной HANDLE (поскольку она управляется).
В любом случае, я хотел бы знать, возможно ли использовать новые функции C ++ 11 для создания единого универсального класса, который можно использовать для переноса любой тип ресурса только предоставляя реализацию для создания и удаления?
Одна проблема, которую я предвижу, состоит в том, что функция создания, такая как отмеченная выше с CreateFile (), может принимать любое количество параметров. Есть ли способ автоматически генерировать шаблонный конструктор, который имитирует сигнатуру функции? Параметры Variadic приходят на ум, но я еще не использовал их.
Кто-нибудь пробовал написать что-то подобное?
РЕДАКТИРОВАТЬ: Некоторый код, чтобы помочь проиллюстрировать (псевдо):
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
, но вместо этого конструктор будет тем, который создает ресурсы созданными разработчиком классами создателя и удалителя. У меня есть ощущение, что 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)...) ); }
};
Одной операцией, которая необходима для более чем основных функций, является возможность «обнулить» дескриптор, позволяя существовать недействительным дескрипторам и позволяя перемещать дескрипторы. 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 closed\n";
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()(...)
просто присваивает ноль вашему T
, который работает для многих типов ручек.
Он поддерживает семантику перемещения, что позволяет вам возвращать их из функции, но я не включил Copier
аргумент (который, как я ожидаю, потребуется для любых объектов RAII, которые можно скопировать).
Живой пример с очень немного другим кодом.
Других решений пока нет …