Object interface
Objects in the Lely core 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_
// A convenience typedef for an opaque object type.
typedef struct obj obj_t;
#ifdef __cplusplus
extern "C" {
#endif
// In C++, memory allocation and object initialization are separate steps. The
// C implementation of these steps is exposed so they can be used separately by
// the C++ interface, if necessary. Users of the C interface SHOULD NOT invoke
// these functions directly, but use obj_create() and obj_destroy() instead.
void *obj_alloc(void);
void obj_free(void *ptr);
obj_t *obj_init(obj_t *obj, Args... args);
void obj_fini(obj_t *obj);
// Creates a new object instance. Internally, this function invokes obj_alloc()
// followed by obj_init().
obj_t *obj_create(Args... args);
// Destroys an object instance. Internally, this function invokes obj_fini()
// followed by obj_free().
void obj_destroy(obj_t *obj);
// An example of an object method. The first parameter is always a pointer to
// the object (cf. "this" in C++).
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 <lely/util/errnum.h>
#include <assert.h>
#include <stdlib.h>
struct obj {
...
};
void *
obj_alloc(void)
{
void *ptr = malloc(sizeof(struct obj));
if (!ptr)
// On POSIX platforms this is a no-op (errno = errno), but on
// Windows this converts errno into a system error code and
// invokes SetLastError().
set_errc(errno2c(errno));
return ptr;
}
void
obj_free(void *ptr)
{
free(ptr);
}
obj_t *
obj_init(obj_t *obj, Args... args)
{
assert(obj);
// Initialize all object members. If an error occurs, clean up and
// return NULL.
...
return obj;
}
void
obj_fini(obj_t *obj)
{
assert(obj);
// Finalize all object members.
...
}
obj_t *
obj_create(Args... args)
{
int errc = 0;
obj_t *obj = obj_alloc();
if (!obj) {
errc = get_errc();
goto error_alloc;
}
obj_t *tmp = obj_init(obj, args...);
if (!tmp) {
errc = get_errc();
goto error_init;
}
obj = tmp;
return obj;
error_init:
obj_free(obj);
error_alloc:
set_errc(errc);
return NULL;
}
void
obj_destroy(obj_t *obj)
{
// obj_destroy() and obj_free() are the only methods which can be called
// with `obj == NULL`.
if (obj) {
obj_fini(obj);
obj_free(obj);
}
}
int
obj_method(obj_t *obj, Args... args)
{
assert(obj);
// Do something with the object.
...
}
Example of a public C++ header (lely/lib/obj.hpp
):
#ifndef LELY_LIB_OBJ_HPP_
#define LELY_LIB_OBJ_HPP_
#include <lely/lib/obj.h>
#include <lely/util/error.hpp>
// Needed for std::swap().
#include <utility>
namespace lely {
namespace lib {
// A non-owning reference to an object. This class is basically a wrapper around
// obj_t*.
class ObjectBase {
public:
explicit ObjectBase(obj_t* obj_) noexcept : obj(obj_) {}
operator obj_t*() const noexcept { return obj; }
int
method(Args... args) noexcept {
return obj_method(*this, args...);
}
...
protected:
obj_t* obj{nullptr};
};
// An owning reference to an object, a bit like std::unique_ptr<obj_t>. When it
// goes out of scope, the instance is destroyed. Ownership can be transferred
// with std::move().
class Object : public ObjectBase {
public:
Object(Args... args) : ObjectBase(obj_create(args...)) {
if (!obj) util::throw_errc("Object");
}
Object(const Object&) = delete;
Object(Object&& other) noexcept : ObjectBase(other.obj) {
other.obj = nullptr;
}
Object& operator=(const Object&) = delete;
Object&
operator=(Object&& other) noexcept {
using ::std::swap;
swap(obj, other.obj);
return *this;
}
~Object() { obj_destroy(*this); }
...
};
} // namespace lib
} // namespace lely
#endif // !LELY_LIB_OBJ_HPP_