Objects in the Lely libraries are all defined and implemented according to the pattern shown here. This pattern is designed to allow a header-only C++ interface for C objects which is as close as possible to a native C++ interface.
Example of a public C header (lely/lib/obj.h):
#ifndef LELY_LIB_OBJ_H_
#define LELY_LIB_OBJ_H_
struct __obj;
#ifndef __cplusplus
typedef struct __obj obj_t;
#endif
#ifdef __cplusplus
extern "C" {
#endif
void *__obj_alloc(void);
void __obj_free(void *ptr);
struct __obj *__obj_init(struct __obj *obj, Args... args);
void __obj_fini(struct __obj *obj);
obj_t *obj_create(Args... args);
void obj_destroy(obj_t *obj);
int obj_method(obj_t *obj, Args... args);
#ifdef __cplusplus
}
#endif
#endif // !LELY_LIB_OBJ_H_
Example of the C implementation (obj.c):
#include <lely/lib/obj.h>
#include <assert.h>
struct __obj {
...
};
void *
__obj_alloc(void)
{
void *ptr = malloc(sizeof(struct __obj));
if (!ptr)
return ptr;
}
void
__obj_free(void *ptr)
{
free(ptr);
}
struct __obj *
__obj_init(struct __obj *obj, Args... args)
{
assert(obj);
...
return obj;
}
void
__obj_fini(struct __obj *obj)
{
assert(obj);
...;
}
obj_t *
obj_create(Args... args)
{
int errc = 0;
obj_t *obj = __obj_alloc();
if (!obj) {
goto error_alloc_obj;
}
if (!__obj_init(obj, args...)) {
goto error_init_obj;
}
return obj;
error_init_obj:
__obj_free(obj);
error_alloc_obj:
return NULL;
}
void
obj_destroy(obj_t *obj)
{
if (obj) {
__obj_fini(obj);
__obj_free(obj);
}
}
int
obj_method(obj_t *obj, Args... args)
{
assert(obj);
...
}
Example of a public C++ header (lely/lib/obj.hpp):
#ifndef LELY_LIB_OBJ_HPP_
#define LELY_LIB_OBJ_HPP_
#ifndef __cplusplus
#error "include <lely/lib/obj.h> for the C interface"
#endif
namespace lely {
class Obj; }
typedef lely::Obj obj_t;
#include <lely/lib/obj.h>
template <>
struct c_type_traits<__obj> {
typedef __obj value_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* pointer;
typedef const value_type* const_pointer;
static void* alloc() noexcept { return __obj_alloc(); }
static void free(void* ptr) noexcept { __obj_free(ptr); }
static pointer
init(pointer p, Args... args) noexcept
{
return __obj_init(p, args...);
}
static void fini(pointer p) noexcept { __obj_fini(p); }
};
class Obj: public incomplete_c_type<__obj> {
typedef incomplete_c_type<__obj> c_base;
public:
Obj(Args... args): c_base(args...) {}
int
method(Args... args) noexcept
{
return obj_method(this, args...);
}
protected:
~Obj() {}
};
}
#endif // !LELY_LIB_OBJ_HPP_
Defining the destructor as protected prevents allocating objects on the stack. Unfortunately, it also prevents invoking delete on heap-allocated objects. Instead, objects must be destroy by invoking destroy(), either as a member function (obj->destroy()) or as a global function (destroy(obj)).
From C++11 onwards, using raw pointers is discouraged. Shared or unique pointers are preferred. lely/util/c_type.hpp provides the make_shared_c() and make_unique_c() convenience functions for this purpose. They are equivalent to the standard make_shared() and make_unique() functions, but specify destroy() as the deleter.