Обертывание функций C в автообъектах без дублирования кода

В 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 может сыграть в этом роль, но я никогда этим не пользовался.

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 мой функциональный объект по умолчанию для «нулевого» дескриптора:

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, которые можно скопировать).

Живой пример с очень немного другим кодом.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]