Returning Custom Classes from C++ GDExtension

Summary

Two things have to be consider to create instances of your class in GDExtension and to use them in GDScript aswell.

Initialization

As @indicainkwell pointed out we have to initialize objects that we want to initialize in our GDExtension but also modify in GDScript via the memnew method.

Meaning if that to create a new instance of my Moment class I could do something like this:

auto* pMoment = memnew(Moment);
pMoment = rawMoment; // Assuming a asign operator exists for these types

An important thing to remember is that we have to free this memory if we don’t need it anymore with a call to memdelete otherwise we create a memory leak. Thus we have to add the following line to our code

memdelete(pMoment);

Edit: This next section is outdated and I later found out that godot already implments such a wrapper, this is explained at the end of the post.

Since this isn’t really up to date with the new C++ standards, I’d advise to create a wrapper that handles the creations of such objects, this could be implemented like this

template<typename T>
class External
{
public:
    External() : pInstance{memnew(T)} {}
    External(const External&) = delete;
    External(External&& other) : pInstance{other.pInstance} { other.pInstance = nullptr; }
    External& operator=(const External&) = delete;
    External& operator=(External&& other) {
        if (this != &other) {
            pInstance = other.pInstance;
            other.pInstance = nullptr;
        }

        return *this;
    }
    ~External() { if (pInstance == nullptr) return; memdelete(pInstance); }

    const T* get() const { return pInstance; }
    T* get() { return pInstance; }

    const T* operator->() const { return pInstance; }
    T* operator->() { return pInstance; }

    template<typename U>
    void assign(const U& other) { *pInstance = other; }
    template<typename U>
    void assign(U&& other) { *pInstance = std::move(other); }

private:
    T* pInstance;
};

With this wrapper we are able to achive the goal of the first to snippets with the following lines of code, and we don’t have to worry about resource leaks

External<Moment> moment;
moment.assign(rawMoment);

Returning and Passing as Argument

To return an Object to GDScript we have to possiblities as shown by @MortalWombat and @indicainkwell.

  • Either by returning a pointer
  • Or a Ref object

E.g.

Moment* at(int64_t index);

or

Ref<Moment> at(int64_t index);

Be aware that if you want to return a Ref than your class has to inherit from RefCounted or a child of it, with the pointer method we are able to return every type that inherits of Object.

Edit: Using Ref instead of memnew

One can skip the whole External thing mentioned above and instead use a create a Ref<Moment> which does all the memory management for us. To do this we can do the following:

Ref<Moment> moment;
moment.instantiate();
moment->operator=(rawMoment);