Skip to content

Commit 07fed6a

Browse files
committed
Allow polymorphism with fine::ResourcePtr
This feature should introduce no breaking change to the existing API, while allowing resources to be subclasses and behave as such. This requires a change to how `ResourceWrapper` works, but since it is an implementation detail, this should not be obvious to users of the library, causing no expected breaking changes. The biggest downside of this feature is a slight increase in unused padding inside the `ResourceWrapper` class. With our now approach, we now add on average `alignof(std::max_align_t) - alignof(T)` unused bytes per resource.
1 parent 144cd7b commit 07fed6a

File tree

5 files changed

+106
-18
lines changed

5 files changed

+106
-18
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,32 @@ class Generator {
285285
If defined, the `destructor` callback is called first, and then the
286286
`T` destructor is called as usual.
287287
288+
To make use of polymorphism with resources, only the base class must be
289+
declared using `FINE_RESOURCE`. To construct derived classes, use the
290+
`fine::make_resource<Base, Derived>(...)` function:
291+
292+
```c++
293+
class Supplier {
294+
public:
295+
virtual ~Supplier() noexcept = default;
296+
297+
virtual std::int64_t supply() = 0;
298+
};
299+
FINE_RESOURCE(Supplier);
300+
301+
class ConstantSupplier final : public Supplier {
302+
public:
303+
ConstantSupplier(std::int64_t constant) : m_constant(constant) {}
304+
305+
std::int64_t supply() { return m_constant; }
306+
307+
private:
308+
std::int64_t m_constant;
309+
};
310+
311+
fine::ResourcePtr<Supplier> supplier = fine::make_resource<Supplier, ConstantSupplier>(INT64_C(42));
312+
```
313+
288314
Oftentimes NIFs deal with classes from third-party packages, in which
289315
case, you may not control how the objects are created and you cannot
290316
add callbacks such as `destructor` to the implementation. If you run

include/fine.hpp

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include <variant>
1616
#include <vector>
1717

18+
#include <iostream>
19+
1820
#include <erl_nif.h>
1921

2022
#if defined(_MSVC_LANG)
@@ -164,17 +166,32 @@ template <typename... Args> class Error {
164166

165167
namespace __private__ {
166168
template <typename T> struct ResourceWrapper {
167-
T resource;
168-
bool initialized;
169+
enum { INITIALIZED = 0 };
170+
171+
union {
172+
struct {
173+
bool initialized;
174+
};
175+
std::max_align_t _unused;
176+
};
177+
178+
const T *resource() const { return reinterpret_cast<const T *>(this + 1); }
179+
180+
T *resource() noexcept { return reinterpret_cast<T *>(this + 1); }
181+
182+
template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
183+
static constexpr std::size_t byte_size() noexcept {
184+
return sizeof(ResourceWrapper) + sizeof(U);
185+
}
169186

170187
static void dtor(ErlNifEnv *env, void *ptr) {
171188
auto resource_wrapper = reinterpret_cast<ResourceWrapper<T> *>(ptr);
172189

173190
if (resource_wrapper->initialized) {
174191
if constexpr (has_destructor<T>::value) {
175-
resource_wrapper->resource.destructor(env);
192+
resource_wrapper->resource()->destructor(env);
176193
}
177-
resource_wrapper->resource.~T();
194+
resource_wrapper->resource()->~T();
178195
}
179196
}
180197

@@ -221,11 +238,11 @@ template <typename T> class ResourcePtr {
221238
return *this;
222239
}
223240

224-
T &operator*() const { return this->ptr->resource; }
241+
T &operator*() const { return *this->ptr->resource(); }
225242

226-
T *operator->() const { return &this->ptr->resource; }
243+
T *operator->() const { return this->ptr->resource(); }
227244

228-
T *get() const { return &this->ptr->resource; }
245+
T *get() const { return this->ptr->resource(); }
229246

230247
friend void swap(ResourcePtr<T> &left, ResourcePtr<T> &right) {
231248
using std::swap;
@@ -241,12 +258,17 @@ template <typename T> class ResourcePtr {
241258
// Friend functions that use the resource_type static member or the
242259
// private constructor.
243260

244-
template <typename U, typename... Args>
261+
template <typename U, typename V, typename... Args, typename>
245262
friend ResourcePtr<U> make_resource(Args &&...args);
246263

264+
template <typename U>
265+
friend Term make_resource_binary(ErlNifEnv *env, ResourcePtr<U> resource,
266+
const char *data, size_t size);
267+
247268
friend class Registration;
248269

249270
friend struct Decoder<ResourcePtr<T>>;
271+
friend struct Encoder<ResourcePtr<T>>;
250272

251273
inline static ErlNifResourceType *resource_type = nullptr;
252274

@@ -255,7 +277,8 @@ template <typename T> class ResourcePtr {
255277

256278
// Allocates a new resource object, invoking its constructor with the
257279
// given arguments.
258-
template <typename T, typename... Args>
280+
template <typename T, typename U = T, typename... Args,
281+
typename = std::enable_if_t<std::is_base_of_v<T, U>>>
259282
ResourcePtr<T> make_resource(Args &&...args) {
260283
auto type = ResourcePtr<T>::resource_type;
261284

@@ -265,10 +288,10 @@ ResourcePtr<T> make_resource(Args &&...args) {
265288
" to register your resource type with the FINE_RESOURCE macro");
266289
}
267290

268-
void *allocation_ptr =
269-
enif_alloc_resource(type, sizeof(__private__::ResourceWrapper<T>));
291+
void *allocation_ptr = enif_alloc_resource(
292+
type, __private__::ResourceWrapper<T>::template byte_size<U>());
270293

271-
auto resource_wrapper =
294+
auto *resource_wrapper =
272295
reinterpret_cast<__private__::ResourceWrapper<T> *>(allocation_ptr);
273296

274297
// We create ResourcePtr right away, to make sure the resource is
@@ -282,7 +305,8 @@ ResourcePtr<T> make_resource(Args &&...args) {
282305

283306
// Invoke the constructor with prefect forwarding to initialize the
284307
// object at the VM-allocated memory
285-
new (&resource_wrapper->resource) T(std::forward<Args>(args)...);
308+
new (reinterpret_cast<U *>(resource_wrapper->resource()))
309+
U(std::forward<Args>(args)...);
286310

287311
resource_wrapper->initialized = true;
288312

@@ -296,8 +320,8 @@ ResourcePtr<T> make_resource(Args &&...args) {
296320
template <typename T>
297321
Term make_resource_binary(ErlNifEnv *env, ResourcePtr<T> resource,
298322
const char *data, size_t size) {
299-
return enif_make_resource_binary(
300-
env, reinterpret_cast<void *>(resource.get()), data, size);
323+
return enif_make_resource_binary(env, reinterpret_cast<void *>(resource.ptr),
324+
data, size);
301325
}
302326

303327
// Creates a binary term copying data from the given buffer.
@@ -811,7 +835,7 @@ template <typename K, typename V> struct Encoder<std::map<K, V>> {
811835

812836
template <typename T> struct Encoder<ResourcePtr<T>> {
813837
static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr<T> &resource) {
814-
return enif_make_resource(env, reinterpret_cast<void *>(resource.get()));
838+
return enif_make_resource(env, reinterpret_cast<void *>(resource.ptr));
815839
}
816840
};
817841

test/c_src/finest.cpp

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#include <cstring>
2-
#include <erl_nif.h>
32
#include <exception>
4-
#include <fine.hpp>
53
#include <optional>
64
#include <stdexcept>
75
#include <thread>
86

7+
#include <erl_nif.h>
8+
#include <fine.hpp>
9+
910
namespace finest {
1011

1112
namespace atoms {
@@ -74,6 +75,30 @@ struct ExError {
7475
static constexpr auto is_exception = true;
7576
};
7677

78+
struct AbstractRes {
79+
virtual ~AbstractRes() noexcept = default;
80+
};
81+
FINE_RESOURCE(AbstractRes);
82+
83+
struct ConcreteRes : AbstractRes {
84+
ErlNifPid pid;
85+
86+
ConcreteRes(ErlNifPid pid) : pid(pid) {}
87+
88+
~ConcreteRes() noexcept override {
89+
auto target_pid = this->pid;
90+
91+
auto thread = std::thread([target_pid] {
92+
auto msg_env = enif_alloc_env();
93+
auto msg = fine::encode(msg_env, atoms::destructor_default);
94+
enif_send(NULL, &target_pid, msg_env, msg);
95+
enif_free_env(msg_env);
96+
});
97+
98+
thread.detach();
99+
}
100+
};
101+
77102
int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; }
78103
FINE_NIF(add, 0);
79104

@@ -194,6 +219,11 @@ fine::Term resource_binary(ErlNifEnv *env,
194219
}
195220
FINE_NIF(resource_binary, 0);
196221

222+
fine::ResourcePtr<AbstractRes> resource_abstract(ErlNifEnv *, ErlNifPid pid) {
223+
return fine::make_resource<AbstractRes, ConcreteRes>(pid);
224+
}
225+
FINE_NIF(resource_abstract, 0);
226+
197227
fine::Term make_new_binary(ErlNifEnv *env) {
198228
const char *buffer = "hello world";
199229
size_t size = 11;

test/lib/finest/nif.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ defmodule Finest.NIF do
4141
def resource_create(_pid), do: err!()
4242
def resource_get(_resource), do: err!()
4343
def resource_binary(_resource), do: err!()
44+
def resource_abstract(_pid), do: err!()
4445

4546
def make_new_binary(), do: err!()
4647

test/test/finest_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ defmodule FinestTest do
252252

253253
assert_receive :destructor_default
254254
end
255+
256+
test "resource can be abstract" do
257+
NIF.resource_abstract(self())
258+
:erlang.garbage_collect(self())
259+
260+
assert_receive :destructor_default
261+
end
255262
end
256263

257264
describe "make_new_binary" do

0 commit comments

Comments
 (0)