ownership¶
Ownership is important when managing resources in C++. sol has many ownership semantics which are generally safe by default. Below are the rules.
object ownership¶
You can take a reference to something that exists in Lua by pulling out a sol::reference or a sol::object:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4#include <string>
5#include <iostream>
6
7int main() {
8 sol::state lua;
9 lua.open_libraries(sol::lib::base);
10
11 lua.script(R"(
12 obj = "please don't let me die";
13 )");
14
15 sol::object keep_alive = lua["obj"];
16 lua.script(R"(
17 obj = nil;
18 function say(msg)
19 print(msg)
20 end
21 )");
22
23 lua.collect_garbage();
24
25 lua["say"](lua["obj"]);
26 // still accessible here and still alive in Lua
27 // even though the name was cleared
28 std::string message = keep_alive.as<std::string>();
29 std::cout << message << std::endl;
30
31 // Can be pushed back into Lua as an argument
32 // or set to a new name,
33 // whatever you like!
34 lua["say"](keep_alive);
35
36 return 0;
37}
All objects must be destroyed before the sol::state is destroyed, otherwise you will end up with dangling references to the Lua State and things will explode in horrible, terrible fashion.
This applies to more than just sol::object: all types derived from sol::reference and sol::object (sol::table sol::userdata, etc.) must be cleaned up before the state goes out of scope.
pointer ownership¶
sol will not take ownership of raw pointers: raw pointers do not own anything. sol will not delete raw pointers, because they do not (and are not supposed to) own anything:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4struct my_type {
5 void stuff() {
6 }
7};
8
9int main() {
10
11 sol::state lua;
12 // AAAHHH BAD
13 // dangling pointer!
14 lua["my_func"] = []() -> my_type* { return new my_type(); };
15
16 // AAAHHH!
17 lua.set("something", new my_type());
18
19 // AAAAAAHHH!!!
20 lua["something_else"] = new my_type();
21 // Acceptable, it will set 'something' to nil
22 // (and delete it on next GC if there's no more references)
23 lua.set("something", nullptr);
24
25 // Also fine
26 lua["something_else"] = nullptr;
27
28 return 0;
29}
Use/return a unique_ptr or shared_ptr instead or just return a value:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4struct my_type {
5 void stuff() {
6 }
7};
8
9int main() {
10
11 sol::state lua;
12 // :ok:
13 lua["my_func0"] = []() -> std::unique_ptr<my_type> {
14 return std::make_unique<my_type>();
15 };
16
17 // :ok:
18 lua["my_func1"] = []() -> std::shared_ptr<my_type> {
19 return std::make_shared<my_type>();
20 };
21
22 // :ok:
23 lua["my_func2"] = []() -> my_type { return my_type(); };
24
25 // :ok:
26 lua.set(
27 "something", std::unique_ptr<my_type>(new my_type()));
28
29 std::shared_ptr<my_type> my_shared
30 = std::make_shared<my_type>();
31 // :ok:
32 // Acceptable, it will set 'something' to nil
33 // (and delete it on next GC if there's no more references)
34 lua.set("something", nullptr);
35
36 // Also fine
37 lua["something_else"] = nullptr;
38
39 return 0;
40}
If you have something you know is going to last and you just want to give it to Lua as a reference, then it’s fine too:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4struct my_type {
5 void stuff() {
6 }
7};
8
9int main() {
10
11 sol::state lua;
12
13 // :ok:
14 auto my_unique = std::make_unique<my_type>();
15 lua["other_thing"] = std::move(my_unique);
16 // Acceptable, it will set 'something' to nil
17 // (and delete it on next GC if there's no more references)
18 lua.set("something", nullptr);
19
20 // Also fine
21 lua["something_else"] = nullptr;
22
23 return 0;
24}
sol can detect nullptr, so if you happen to return it there won’t be any dangling because a sol::lua_nil will be pushed. But if you know it’s nil beforehand, please return std::nullptr_t or sol::lua_nil:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4struct my_type {
5 void stuff() {
6 }
7};
8
9int main() {
10
11 sol::state lua;
12 // :ok:
13 lua["my_func5"] = []() -> my_type* {
14 static my_type mt;
15 return &mt;
16 };
17
18 // THIS IS STILL BAD DON'T DO IT AAAHHH BAD
19 // return a unique_ptr that's empty instead
20 // or be explicit!
21 lua["my_func6"] = []() -> my_type* { return nullptr; };
22
23 // :ok:
24 lua["my_func7"]
25 = []() -> std::nullptr_t { return nullptr; };
26
27 // :ok:
28 lua["my_func8"] = []() -> std::unique_ptr<my_type> {
29 // default-constructs as a nullptr,
30 // gets pushed as nil to Lua
31 return std::unique_ptr<my_type>();
32 // same happens for std::shared_ptr
33 };
34
35 // Acceptable, it will set 'something' to nil
36 // (and delete it on next GC if there's no more references)
37 lua.set("something", nullptr);
38
39 // Also fine
40 lua["something_else"] = nullptr;
41
42 return 0;
43}
ephermeal (proxy) objects¶
Proxy and result types are ephermeal. They rely on the Lua stack and their constructors / destructors interact with the Lua stack. This means they are entirely unsafe to return from functions in C++, without very careful attention paid to how they are used that often requires relying on implementation-defined behaviors.
Please be careful when using (protected_)function_result, load_result (especially multiple load/function results in a single C++ function!) stack_reference, and similar stack-based things. If you want to return these things, consider