Template class for wrapping C resources in RAII
CWrapper is one class that you can use to wrap C resource management. Designed to be easy-to-use, and compatible with most common C practices.
It generates
- constructor
- move constructor
- move assignment operator
- destructor
for you automatically. You can choose whether copy constructor should be deleted or generated.
Optionally, operator->
and conversion operators can be generated, with or without const safety.
CWrapper is one header file, with no dependencies, you can use it for any C library.
However, it higly relies on C++11 features, so you will need a C++11-compatible compiler.
Declaration:
template<
typename HANDLE_T,
typename FUNCTIONS,
CWrapperType TYPE = CWrapperType::Get,
bool CONSTSAFE = true>
using CWrapper = /* internal template magic */;
Type of handle to wrap, example: FILE*
. HANDLE_T
must be copy-constructable and copy-assignable. There must be a possible value of HANDLE_T
indicating that the handle is invalid, for example, nullptr
for pointers. This invalid value is customizable.
If HANDLE_T
is a pointer type, operator->
will be generated for the wrapper.
This shall be a class that has static functions called ctor_func
and dtor_func
, these will be called in constructor and destructor. If FUNCTIONS
has a static function called copy_func
, CWrapper will also generate copy constructor and copy assignment operator. Optionally, invalid_value
static member, validate_func
static function and exception
nested type can be specified. None of these functions are allowed to throw an exception.
However, it should be called CONTROLLER
as this term describes its behavior better.
invalid_value
shall be an instance ofHANDLE_T
or convertible toHANDLE_T
. Handle is set toinvalid_value
after moving out the handle from an rvalue reference. It has a default value:nullptr
ifHANDLE_T
is a pointer, and0
otherwise.validate_func
shall have oneHANDLE_T
parameter. It is called after allctor_func
andcopy_func
calls. It shall returntrue
if the handle passed as parameter is valid,false
otherwise. It has a default value, checks if parameter isinvalid_value
.ctor_func
shall return withHANDLE_T
. It may have any parameters exceptHANDLE_T
, and it may also be overloaded. The return value will be checked withvalidate_func
.dtor_func
shall have oneHANDLE_T
parameter, its return value is discarded. The function is called only ifvalidate_func
returnedtrue
.copy_func
shall return withHANDLE_T
and it shall have oneconst HANDLE_T
parameter. The return value will be checked withvalidate_func
.exception
is thrown (with its default constructor) wheneverctor_func
orcopy_func
returns an invalid handle. Its default type isstd::bad_alloc
.- This seems a bad limit that the default constructor is called, but this is more flexible than you think. Just make a custom exception type with a more informative default constructor.
Example:
class stdc_error : public std::exception
{
int error_number = errno;
public:
virtual const char * what() const noexcept override
{
return strerror(error_number);
}
};
However, it is not required use actual functions, they can be any function objects, like std::function
, function pointers or auto variables: static constexpr auto ctor_func = fopen;
Its type is CWrapperType
:
enum class CWrapperType
{
Implicit,
Explicit,
Get,
};
Implicit
: CWrapper will generate an implicit conversion operator toHANDLE_T
.Explicit
: CWrapper will generate an explicit conversion operator toHANDLE_T
.Get
: CWrapper will generate a member function called.get()
that returnsHANDLE_T
.
TYPE
is CWrapperType::Get
by default.
If CONSTSAFE
is true
and HANDLE_T
is a pointer type, CWrapper will have two conversion operators and two operator->
.
For example, if TYPE
is CWrapperType::Get
, wrapper will have the following functions:
PTR_T* get();
PTR_T const* get() const;
PTR_T* operator->();
PTR_T const* operator->() const;
If CONSTSAFE
is false
and HANDLE_T
is a pointer type, wrapper will have these functions:
PTR_T* get() const;
PTR_T* operator->() const;
If HANDLE_T
is not a pointer type, CONSTSAFE
has no effect, and wrapper will have this function:
HANDLE_T get() const;
CONSTSAFE
is true
by default.
CWrapper's main purpose is to hide resource management in RAII, but keep the behaviour of the original handle. It is not designed for converting all global handling functions to member functions, it is your job! However, CWrapper makes it easier, I will add examples showing this.
This is not really an issue of CWrapper
, but it must be noted: dtor_func
should never be called directly.
using FileWrapper = CWrapper<FILE*, FileWrapperFunctions, CWrapperType::Implicit, false>;
int main()
{
FileWrapper file("example.txt", "r");
// use file
fclose(file); // <- ERROR
}
The destructor of file will call fclose
again on the internal pointer, so this will result in a crash.
- Do not use implicit conversion, so there is a smaller chance that you will call
fclose
without paying attention. - Delete the function:
void fclose(FileWrapper const&) = delete;
Currently there is no elegant solution if you really need to close a wrapper, to call its destructor function explicitly. This might be inevitable for example if it is a global variable.
{ FileWrapper temp = std::move(file); }
Current main.cpp
wraps FILE*
as it is the only C resource management in the standard library.
I will add some more examples soon, using various libraries like SDL2, iniparser and pcap.