Lely core libraries  1.9.2
doc/util/object.md
1 Object interface
2 ================
3 
4 Objects in the Lely libraries are all defined and implemented according to the
5 pattern shown here. This pattern is designed to allow a header-only C++
6 interface for C objects which is as close as possible to a native C++ interface.
7 
8 Example of a public C header (lely/lib/obj.h):
9 ~~~{.c}
10 #ifndef LELY_LIB_OBJ_H_
11 #define LELY_LIB_OBJ_H_
12 
13 // The obj_t typedef is only defined for C. This allows the C++ interface to
14 // define its own typedef but still call all C functions without requiring a
15 // cast.
16 struct __obj;
17 #ifndef __cplusplus
18 typedef struct __obj obj_t;
19 #endif
20 
21 #ifdef __cplusplus
22 // Disable C++ name mangling and ensure the C calling convention is used.
23 extern "C" {
24 #endif
25 
26 // In C++, memory allocation and object initialization are separate steps. The
27 // C implementation of these steps is exposed so they can be used by the C++
28 // interface. Users of the C interface SHOULD not invoke these functions
29 // directly, but use obj_create() and obj_destroy() instead.
30 void *__obj_alloc(void);
31 void __obj_free(void *ptr);
32 struct __obj *__obj_init(struct __obj *obj, Args... args);
33 void __obj_fini(struct __obj *obj);
34 
35 // Creates a new object instance. Internally, this function invokes
36 // __obj_alloc() followed by __obj_init().
37 obj_t *obj_create(Args... args);
38 
39 // Destroys an object instance. Internally, this function invokes __obj_fini()
40 // followed by obj_free().
41 void obj_destroy(obj_t *obj);
42 
43 // An example of an object method. The first parameter is always a pointer to
44 // the object (cf. "this" in C++).
45 int obj_method(obj_t *obj, Args... args);
46 
47 #ifdef __cplusplus
48 }
49 #endif
50 
51 #endif // !LELY_LIB_OBJ_H_
52 ~~~
53 
54 Example of the C implementation (obj.c):
55 ~~~{.c}
56 #include <lely/util/errnum.h>
57 #include <lely/lib/obj.h>
58 
59 #include <assert.h>
60 #include <stdlib.h>
61 
62 struct __obj {
63  ...
64 };
65 
66 void *
67 __obj_alloc(void)
68 {
69  void *ptr = malloc(sizeof(struct __obj));
70  if (!ptr)
71  // On POSIX platforms this is a no-op (errno = errno), but on
72  // Windows this converts errno into a system error code and
73  // invokes SetLastError().
74  set_errc(errno2c(errno));
75  return ptr;
76 }
77 
78 void
79 __obj_free(void *ptr)
80 {
81  free(ptr);
82 }
83 
84 struct __obj *
85 __obj_init(struct __obj *obj, Args... args)
86 {
87  assert(obj);
88 
89  // Initialize all object members. If an error occurs, clean up and
90  // return NULL.
91  ...
92 
93  return obj;
94 }
95 
96 void
97 __obj_fini(struct __obj *obj)
98 {
99  assert(obj);
100 
101  // Finalize all object members.
102  ...;
103 }
104 
105 obj_t *
106 obj_create(Args... args)
107 {
108  int errc = 0;
109 
110  obj_t *obj = __obj_alloc();
111  if (!obj) {
112  errc = get_errc();
113  goto error_alloc_obj;
114  }
115 
116  if (!__obj_init(obj, args...)) {
117  errc = get_errc();
118  goto error_init_obj;
119  }
120 
121  return obj;
122 
123 error_init_obj:
124  __obj_free(obj);
125 error_alloc_obj:
126  set_errc(errc);
127  return NULL;
128 }
129 
130 void
131 obj_destroy(obj_t *obj)
132 {
133  // obj_destroy() and obj_free() are the only methods which can be called
134  // with `obj == NULL`.
135  if (obj) {
136  __obj_fini(obj);
137  __obj_free(obj);
138  }
139 }
140 
141 int
142 obj_method(obj_t *obj, Args... args)
143 {
144  assert(obj);
145 
146  // Do something with the object.
147  ...
148 }
149 ~~~
150 
151 Example of a public C++ header (lely/lib/obj.hpp):
152 ~~~{.cpp}
153 #ifndef LELY_LIB_OBJ_HPP_
154 #define LELY_LIB_OBJ_HPP_
155 
156 #ifndef __cplusplus
157 #error "include <lely/lib/obj.h> for the C interface"
158 #endif
159 
160 // Include the incomplete_c_type<> and c_type_traits<> templates.
161 #include <lely/util/c_type.hpp>
162 
163 // Provide the obj_t typedef. This is necessary to include lely/lib/obj.h
164 // without errors.
165 namespace lely { class Obj; }
166 typedef lely::Obj obj_t;
167 
168 #include <lely/lib/obj.h>
169 
170 namespace lely {
171 
172 // The c_type_traits<> template specialization provides a uniform wrapper for
173 // the the alloc(), free(), init() and fini() methods from the C interface.
174 // These methods are invoked by incomplete_c_type<> from which the C++ interface
175 // inherits.
176 template <>
177 struct c_type_traits<__obj> {
178  typedef __obj value_type;
179  typedef value_type& reference;
180  typedef const value_type& const_reference;
181  typedef value_type* pointer;
182  typedef const value_type* const_pointer;
183 
184  static void* alloc() noexcept { return __obj_alloc(); }
185  static void free(void* ptr) noexcept { __obj_free(ptr); }
186 
187  static pointer
188  init(pointer p, Args... args) noexcept
189  {
190  return __obj_init(p, args...);
191  }
192 
193  static void fini(pointer p) noexcept { __obj_fini(p); }
194 };
195 
196 class Obj: public incomplete_c_type<__obj> {
197  typedef incomplete_c_type<__obj> c_base;
198 public:
199  // c_base calls the alloc() and init() methods of c_type_traits<__obj>
200  // that construct the object.
201  Obj(Args... args): c_base(args...) {}
202 
203  int
204  method(Args... args) noexcept
205  {
206  // Due to the (re)definition of obj_t we can pass "this" as the
207  // first argument.
208  return obj_method(this, args...);
209  }
210 
211 protected:
212  // The destructor is protected to prevent the creation of an instance on
213  // the heap. This would lead to errors, since the size of the object is
214  // unknown.
215  ~Obj() {}
216 };
217 
218 } // lely
219 
220 #endif // !LELY_LIB_OBJ_HPP_
221 ~~~
222 
223 Defining the destructor as `protected` prevents allocating objects on the stack.
224 Unfortunately, it also prevents invoking `delete` on heap-allocated objects.
225 Instead, objects must be destroy by invoking `destroy()`, either as a member
226 function (`obj->destroy()`) or as a global function (`destroy(obj)`).
227 
228 From C++11 onwards, using raw pointers is discouraged. Shared or unique pointers
229 are preferred. lely/util/c_type.hpp provides the `make_shared_c()` and
230 `make_unique_c()` convenience functions for this purpose. They are equivalent to
231 the standard `make_shared()` and `make_unique()` functions, but specify
232 `destroy()` as the deleter.
233