diff --git a/README.md b/README.md
index 6574678..9a3e3cd 100644
--- a/README.md
+++ b/README.md
@@ -287,6 +287,32 @@ class Generator {
If defined, the `destructor` callback is called first, and then the
`T` destructor is called as usual.
+To make use of polymorphism with resources, only the base class must be
+declared using `FINE_RESOURCE`. To construct derived classes, use the
+`fine::make_resource(...)` function:
+
+```c++
+class Supplier {
+public:
+ virtual ~Supplier() noexcept = default;
+
+ virtual std::int64_t supply() = 0;
+};
+FINE_RESOURCE(Supplier);
+
+class ConstantSupplier final : public Supplier {
+public:
+ ConstantSupplier(std::int64_t constant) : m_constant(constant) {}
+
+ std::int64_t supply() { return m_constant; }
+
+private:
+ std::int64_t m_constant;
+};
+
+fine::ResourcePtr supplier = fine::make_resource(INT64_C(42));
+```
+
Oftentimes NIFs deal with classes from third-party packages, in which
case, you may not control how the objects are created and you cannot
add callbacks such as `destructor` to the implementation. If you run
diff --git a/include/fine.hpp b/include/fine.hpp
index 4c64469..846b3cc 100644
--- a/include/fine.hpp
+++ b/include/fine.hpp
@@ -164,17 +164,36 @@ template class Error {
namespace __private__ {
template struct ResourceWrapper {
- T resource;
- bool initialized;
+ union {
+ struct {
+ bool initialized;
+ } data;
+ std::max_align_t _unused;
+ } payload;
+
+ bool initialized() const { return payload.data.initialized; }
+
+ void set_initialized(bool initialized) {
+ payload.data.initialized = initialized;
+ }
+
+ const T *resource() const { return reinterpret_cast(this + 1); }
+
+ T *resource() noexcept { return reinterpret_cast(this + 1); }
+
+ template >>
+ static constexpr std::size_t byte_size() noexcept {
+ return sizeof(ResourceWrapper) + sizeof(U);
+ }
static void dtor(ErlNifEnv *env, void *ptr) {
auto resource_wrapper = reinterpret_cast *>(ptr);
- if (resource_wrapper->initialized) {
+ if (resource_wrapper->initialized()) {
if constexpr (has_destructor::value) {
- resource_wrapper->resource.destructor(env);
+ resource_wrapper->resource()->destructor(env);
}
- resource_wrapper->resource.~T();
+ resource_wrapper->resource()->~T();
}
}
@@ -221,11 +240,11 @@ template class ResourcePtr {
return *this;
}
- T &operator*() const { return this->ptr->resource; }
+ T &operator*() const { return *this->ptr->resource(); }
- T *operator->() const { return &this->ptr->resource; }
+ T *operator->() const { return this->ptr->resource(); }
- T *get() const { return &this->ptr->resource; }
+ T *get() const { return this->ptr->resource(); }
friend void swap(ResourcePtr &left, ResourcePtr &right) {
using std::swap;
@@ -241,12 +260,17 @@ template class ResourcePtr {
// Friend functions that use the resource_type static member or the
// private constructor.
- template
+ template
friend ResourcePtr make_resource(Args &&...args);
+ template
+ friend Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource,
+ const char *data, size_t size);
+
friend class Registration;
friend struct Decoder>;
+ friend struct Encoder>;
inline static ErlNifResourceType *resource_type = nullptr;
@@ -255,7 +279,8 @@ template class ResourcePtr {
// Allocates a new resource object, invoking its constructor with the
// given arguments.
-template
+template >>
ResourcePtr make_resource(Args &&...args) {
auto type = ResourcePtr::resource_type;
@@ -265,10 +290,10 @@ ResourcePtr make_resource(Args &&...args) {
" to register your resource type with the FINE_RESOURCE macro");
}
- void *allocation_ptr =
- enif_alloc_resource(type, sizeof(__private__::ResourceWrapper));
+ void *allocation_ptr = enif_alloc_resource(
+ type, __private__::ResourceWrapper::template byte_size());
- auto resource_wrapper =
+ auto *resource_wrapper =
reinterpret_cast<__private__::ResourceWrapper *>(allocation_ptr);
// We create ResourcePtr right away, to make sure the resource is
@@ -278,13 +303,14 @@ ResourcePtr make_resource(Args &&...args) {
// We use a wrapper struct with an extra field to track if the
// resource has actually been initialized. This way if the constructor
// below throws, we can skip the destructor calls in the Erlang dtor
- resource_wrapper->initialized = false;
+ resource_wrapper->set_initialized(false);
// Invoke the constructor with prefect forwarding to initialize the
// object at the VM-allocated memory
- new (&resource_wrapper->resource) T(std::forward(args)...);
+ new (reinterpret_cast(resource_wrapper->resource()))
+ U(std::forward(args)...);
- resource_wrapper->initialized = true;
+ resource_wrapper->set_initialized(true);
return resource;
}
@@ -296,8 +322,8 @@ ResourcePtr make_resource(Args &&...args) {
template
Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource,
const char *data, size_t size) {
- return enif_make_resource_binary(
- env, reinterpret_cast(resource.get()), data, size);
+ return enif_make_resource_binary(env, reinterpret_cast(resource.ptr),
+ data, size);
}
// Creates a binary term copying data from the given buffer.
@@ -811,7 +837,7 @@ template struct Encoder> {
template struct Encoder> {
static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr &resource) {
- return enif_make_resource(env, reinterpret_cast(resource.get()));
+ return enif_make_resource(env, reinterpret_cast(resource.ptr));
}
};
diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp
index ddb3cd2..1c408fb 100644
--- a/test/c_src/finest.cpp
+++ b/test/c_src/finest.cpp
@@ -76,6 +76,30 @@ struct ExError {
static constexpr auto is_exception = true;
};
+struct AbstractRes {
+ virtual ~AbstractRes() noexcept = default;
+};
+FINE_RESOURCE(AbstractRes);
+
+struct ConcreteRes : AbstractRes {
+ ErlNifPid pid;
+
+ ConcreteRes(ErlNifPid pid) : pid(pid) {}
+
+ ~ConcreteRes() noexcept override {
+ auto target_pid = this->pid;
+
+ auto thread = std::thread([target_pid] {
+ auto msg_env = enif_alloc_env();
+ auto msg = fine::encode(msg_env, atoms::destructor_default);
+ enif_send(NULL, &target_pid, msg_env, msg);
+ enif_free_env(msg_env);
+ });
+
+ thread.detach();
+ }
+};
+
int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; }
FINE_NIF(add, 0);
@@ -196,6 +220,11 @@ fine::Term resource_binary(ErlNifEnv *env,
}
FINE_NIF(resource_binary, 0);
+fine::ResourcePtr resource_abstract(ErlNifEnv *, ErlNifPid pid) {
+ return fine::make_resource(pid);
+}
+FINE_NIF(resource_abstract, 0);
+
fine::Term make_new_binary(ErlNifEnv *env) {
const char *buffer = "hello world";
size_t size = 11;
diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex
index 371ade0..2da3dad 100644
--- a/test/lib/finest/nif.ex
+++ b/test/lib/finest/nif.ex
@@ -41,6 +41,7 @@ defmodule Finest.NIF do
def resource_create(_pid), do: err!()
def resource_get(_resource), do: err!()
def resource_binary(_resource), do: err!()
+ def resource_abstract(_pid), do: err!()
def make_new_binary(), do: err!()
diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs
index dc3bc9b..64d7cef 100644
--- a/test/test/finest_test.exs
+++ b/test/test/finest_test.exs
@@ -252,6 +252,13 @@ defmodule FinestTest do
assert_receive :destructor_default
end
+
+ test "resource can be abstract" do
+ NIF.resource_abstract(self())
+ :erlang.garbage_collect(self())
+
+ assert_receive :destructor_default
+ end
end
describe "make_new_binary" do