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_valueshall be an instance ofHANDLE_Tor convertible toHANDLE_T. Handle is set toinvalid_valueafter moving out the handle from an rvalue reference. It has a default value:nullptrifHANDLE_Tis a pointer, and0otherwise.validate_funcshall have oneHANDLE_Tparameter. It is called after allctor_funcandcopy_funccalls. It shall returntrueif the handle passed as parameter is valid,falseotherwise. It has a default value, checks if parameter isinvalid_value.ctor_funcshall 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_funcshall have oneHANDLE_Tparameter, its return value is discarded. The function is called only ifvalidate_funcreturnedtrue.copy_funcshall return withHANDLE_Tand it shall have oneconst HANDLE_Tparameter. The return value will be checked withvalidate_func.exceptionis thrown (with its default constructor) wheneverctor_funcorcopy_funcreturns 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
fclosewithout 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.