Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Behavior of types created in C++ when they go out of scope in Julia #403

Closed
sloede opened this issue Jan 22, 2024 · 4 comments · Fixed by #406
Closed

Behavior of types created in C++ when they go out of scope in Julia #403

sloede opened this issue Jan 22, 2024 · 4 comments · Fixed by #406

Comments

@sloede
Copy link
Contributor

sloede commented Jan 22, 2024

According to the README.md,

The default constructor and any manually added constructor using the constructor function will automatically create a Julia object that has a finalizer attached that calls delete to free the memory. To write a C++ function that returns a new object that can be garbage-collected in Julia, use the jlcxx::create function [...]

From this description it is not 100% clear to me what happens with types that are constructed by a library function on the C++ side that I do not control myself - will they also automatically have a finalizer attached?

For example, if on the C++ side I have something like

struct S {
  int value;

  S(int value_) : value(value_) {}
};

S foo() {
   return S(3);
}

and I wrap this as

#include "jlcxx/jlcxx.hpp"

JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
{
  mod.add_type<S>("S");
  mod.method("foo", &foo);
}

then technically calling foo() in Julia is neither here nor there:

  • it is not created by a default constructor I wrapped,
  • nor by a manually added constructor,
  • and neither uses jlcxx::create

So far I have just assumed that CxxWrap.jl does The Right Thing and will automatically attach a finalizer to it, and thus once GC kicks in, the memory on the C++ side is freed as well. (Re-)reading the documentation again left me wondering if I am being overly optimistic about that.

@barche
Copy link
Collaborator

barche commented Jan 25, 2024

I double-checked, and yes, in the case where a function returns an object by value like in your example, the wrapped object gets a finalizer and is owned by Julia. If a reference or pointer is returned then the default assumption is that the pointed-to object lifetime is managed by C++.

@sloede
Copy link
Contributor Author

sloede commented Jan 26, 2024

Thank you very much - this makes a lot of sense 👍 If it is not too much to asked, it would be great if you could link the relevant code sections in here such that I (start to) understand better how libcxxwrap-julia and CxxWrap.jl work. Either way, feel free to close this issue any time.

P.S.: Just to be sure: returning a shared_ptr is not considered to be "return-by-pointer". That is, a finalizer will be added to a smart pointer such that when it goes out of scope, the C++ side is notified that the reference has been deleted and can decrease its own reference counter. Am I right in this assumpation?

@barche
Copy link
Collaborator

barche commented Jan 26, 2024

The code where the by-value objects are returned is here:

https://github.com/JuliaInterop/libcxxwrap-julia/blob/513d33f55819aad87e68235a3c229c456830e91f/include/jlcxx/type_conversion.hpp#L707

And indeed, it is the same for a shared_ptr!

@sloede
Copy link
Contributor Author

sloede commented Jan 27, 2024

And indeed, it is the same for a shared_ptr!

Thanks for the confirmation! I've created #406 to document this for future reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants