GDExtension C++ - managing RefCounted pointers

Godot Version

v4.3.stable.official [77dcf97d8]

Question

In a GDExtension object (C++), how do I properly store a pointer to another RefCounted object? Is there a guideline? Because I couldn’t find one.

In my specific usecase, my C++ -written class receives a pointer to another object, that inherits from RefCounted. How do I ensure that that other object doesn’t get freed while I still need it?

My obvious thought is to call its “reference()” method just after obtaining the pointer and then “unreference()” when I no longer need it (e.g. in the destructor of my class). But it’s one of these core things which I really need to know for sure, not guessing. Especially since documentations of these methods say “Use this only if you really know what you are doing.” And I don’t.

1 Like
  1. What choices have led you to use C++ for this?
  2. What’s the code look like?
  3. What’s in you RefCounted object?
  4. What is your RefCounted object being used for?
  5. Can you create a C++ object that does the same thing?

C++ requires you to manage your own memory. Full stop. If you do not know what that entails, I strongly recommend you go do some research on it, because answers you get on a forum are unlikely to solve your problem without a thorough knowledge of what you’re asking.

I think you misunderstood me. I’m not asking about how to manage memory in C++ in general. I have enough experience in this language, to be fully aware how important it is to do it properly. And precisely because of that, I know I can’t just take a raw pointer and call it a day - I must ensure the object stays alive at least as long as I’m accessing said pointer. And since Godot seems to have its own way of doing it (nothing wrong about that, many projects do it), namely RefCounted, I know I must understand how to use this specific mechanism. And this is where I’m stuck, as the documentation (RefCounted — Godot Engine (stable) documentation in English) doesn’t really say anything about it.

Perhaps the missing information I should’ve mentioned, is that the object is instantiated in GDScript and then passed to the C++ code as a parameter. Specifically, it goes this way:

var rng = RandomNumberGenerator.new()
var my_object = MyObject.new()
my_object.set_rng(rng)

This is how this method looks in C++:

void MyObject::set_rng(RandomNumberGenerator *rng) {
    this->rng = rng;
}

In the GDExtension documentation I didn’t see an example of passing an object to the C++ code, so for the sake of experiment, I used a raw pointer - and it worked. But storing it for later just like that is obviously dangerous, because the pointed object might easily be freed too early, especially when the code gets more complex.

If it was all written just by me, I’d just utilize shared_ptrs. But the object is created and freed by Godot, so I must follow its way. And that’s what I’m asking about.

(I have my own educated guess that I should use “reference” and “unreference” methods akin to e.g. how “Py_INCREF”/“Py_DECREF” are used in Python/C API or “AddRef”/“Release” are used in COM interfaces, but precisely because it’s about memory management, I can’t just go with guessing. If you know that this is, or is not the case, please let me know).

1 Like

I think I undestand now how it’s supposed to be used. It’s not documented as a part of GDExtension, but there’s a short section in the documentation of the engine architecture, “Object” page, “References” subsection.

And what the short example there shows, it seems using godot::Ref template (godot_cpp/classes/ref.hpp) is a way to go. Judging from its implementation, it’s a type of smart pointer that takes a raw pointer to a RefCounted instance, and indeed internally calls the “reference()”/“unreference()” methods when appropriate (to ensure the object stays alive at least as long as the reference exists).

So, I’m leaving it here, just in case somebody gets stuck at this point like I was.

1 Like

RefCounted objects should be stored using the Ref<T> template class (defined in the same header as RefCounted). Ref<T> automatically calls RefCounted::reference() when it is assigned a value, and calls RefCounted::unreference() in its destructor (i.e. when the Ref goes out of scope, or when the class or container its stored in is destroyed).

Ref<T> can be used more or less like a pointer. You can assign a pointer directly using its overloaded operator= method and dereference it using its overloaded operator* and operator-> methods.

e.g.

Ref<Mesh> mesh = mesh_instance.get_mesh();  // automatically unreferenced when mesh goes out of scope
int n_surfaces = mesh->get_surface_count(); // Object being referenced can be accessed as if it were a pointer
for(int i=0; i < n_surfaces; i++) {
    Ref<Material> mat = mesh->surface_get_material(i);  // automatically unreferenced at end of loop iteration
} 

Ref<ArrayMesh> array_mesh; // a null reference
array_mesh.instantiate(); // now references a new ArrayMesh created on the heap.

This is specifically for objects which inherit from RefCounted. Objects which inherit from Node follow different memory management semantics. You create them on the heap using memnew() and add them to the scene tree as a child of an existing node by calling add_child(). It will be freed automatically when its parent is destroyed, or you can call its queue_free() method to free it sooner. References can be stored as an ordinary pointer (they cannot be local variables on the stack). If a Node lives outside the scene tree, then I believe you are responsible for freeing it manually.

2 Likes

Thanks for confirmation @riotdoggo :slightly_smiling_face: I believe my main mistake was trying to rely too much on GDExtension specification and not going directly to the engine and its sources.

Regarding what you wrote about not allocating Nodes on the stack, I’m wondering whether the same limitation applies to RefCounted? From what I can see, RefCounted actually doesn’t do any deallocation on its own, memdelete is called from Ref::~Ref(), but only if RefCounted::unreference() tells it to do so… meaning it should be safe to pass all RefCounted objects wrapped in Ref<>, regardless of how were they allocated? (obviously, for a stack allocated object, I’d still need to ensure that Refs don’t outlive the object’s scope).

2 Likes