From a74e1da6674c8cb13ab3f570efddce7f69282934 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 18:53:53 -0800 Subject: [PATCH 1/8] Add reference test. --- test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4db13ae0..a90f160f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable(${PROJECT_NAME} "test_Overloads.cpp" "test_Ownership.cpp" "test_Proc.cpp" + "test_Reference.cpp" "test_Self.cpp" "test_String.cpp" "test_Struct.cpp" From 2a8795e42f36214945bcd0ee21eb58d10a30b1aa Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 18:54:16 -0800 Subject: [PATCH 2/8] Removed incorrectly checked in file --- test/build_rice.bat | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test/build_rice.bat diff --git a/test/build_rice.bat b/test/build_rice.bat deleted file mode 100644 index f4cd7c1c..00000000 --- a/test/build_rice.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -call "C:\Program Files\Microsoft Visual Studio\18\Insiders\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1 -cd /d C:\Source\rice -cmake --build --preset msvc-debug From bee653ee651788211527ea0a893d2595210a3217 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 18:54:38 -0800 Subject: [PATCH 3/8] Add Object#extend. --- rice/cpp_api/Object.hpp | 6 ++++++ rice/cpp_api/Object.ipp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/rice/cpp_api/Object.hpp b/rice/cpp_api/Object.hpp index f06d5464..96f7ea3e 100644 --- a/rice/cpp_api/Object.hpp +++ b/rice/cpp_api/Object.hpp @@ -9,6 +9,7 @@ namespace Rice { class Class; + class Module; class String; class Array; @@ -116,6 +117,11 @@ namespace Rice */ bool is_a(Object klass) const; + //! Extend the object with a module. + /*! \param mod the module to extend with. + */ + void extend(Module const& mod); + //! Determine if the objects responds to a method. /*! \param id the name of the method * \return true if the objects responds to the method, false diff --git a/rice/cpp_api/Object.ipp b/rice/cpp_api/Object.ipp index 6bcad660..e08b8681 100644 --- a/rice/cpp_api/Object.ipp +++ b/rice/cpp_api/Object.ipp @@ -99,6 +99,11 @@ namespace Rice return RB_TEST(result); } + inline void Object::extend(Module const& mod) + { + detail::protect(rb_extend_object, this->value(), mod.value()); + } + inline bool Object::respond_to(Identifier id) const { return bool(rb_respond_to(this->value(), id.id())); From 57d7a69a1c7b99e6a0fca86527680f78be2f80a4 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 19:13:56 -0800 Subject: [PATCH 4/8] Add helper methods to lookup Natives by class and type. --- rice/detail/NativeRegistry.hpp | 1 + rice/detail/NativeRegistry.ipp | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/rice/detail/NativeRegistry.hpp b/rice/detail/NativeRegistry.hpp index a4e0d78c..5ba30268 100644 --- a/rice/detail/NativeRegistry.hpp +++ b/rice/detail/NativeRegistry.hpp @@ -35,6 +35,7 @@ namespace Rice::detail const std::vector lookup(VALUE klass); const std::vector>& lookup(VALUE klass, ID methodId); + std::vector lookup(VALUE klass, NativeKind kind); private: // Key - Ruby klass/method diff --git a/rice/detail/NativeRegistry.ipp b/rice/detail/NativeRegistry.ipp index 06f540e5..2aff108c 100644 --- a/rice/detail/NativeRegistry.ipp +++ b/rice/detail/NativeRegistry.ipp @@ -72,4 +72,33 @@ namespace Rice::detail // Lookup items for method return this->natives_[key]; } + + inline std::vector NativeRegistry::lookup(VALUE klass, NativeKind kind) + { + std::vector result; + + if (rb_type(klass) == T_ICLASS) + { + klass = detail::protect(rb_class_of, klass); + } + + for (auto& pair : this->natives_) + { + const std::pair& key = pair.first; + + if (klass == key.first) + { + const std::vector>& natives = pair.second; + for (auto& native : natives) + { + if (native->kind() == kind) + { + result.push_back(native->name()); + } + } + } + } + + return result; + } } From d7ed15ec3268f25cd0b7dbb34c5b3d5437d28b4a Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 19:16:03 -0800 Subject: [PATCH 5/8] Protect ruby_mark calls from exceptions. --- rice/Data_Type.ipp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rice/Data_Type.ipp b/rice/Data_Type.ipp index c124931e..8da6269a 100644 --- a/rice/Data_Type.ipp +++ b/rice/Data_Type.ipp @@ -5,17 +5,22 @@ namespace Rice template inline void ruby_mark_internal(detail::WrapperBase* wrapper) { - // Tell the wrapper to mark the objects its keeping alive - wrapper->ruby_mark(); - - // Get the underlying data and call custom mark function (if any) - T* data = static_cast(wrapper->get()); - ruby_mark(data); + detail::cpp_protect([&] + { + // Tell the wrapper to mark the objects its keeping alive + wrapper->ruby_mark(); + + // Get the underlying data and call custom mark function (if any) + // Use the wrapper's stored rb_data_type to avoid type mismatch + T* data = static_cast(wrapper->get(wrapper->rb_data_type_)); + ruby_mark(data); + }); } template inline void ruby_free_internal(detail::WrapperBase* wrapper) { + // Destructors are noexcept so we cannot use cpp_protect here delete wrapper; } From a7ed1fcc42a1f7b1efc9bb583e069274315e6397 Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 19:16:28 -0800 Subject: [PATCH 6/8] Formatting. --- test/test_Inheritance.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test_Inheritance.cpp b/test/test_Inheritance.cpp index 19c8a35f..4874e58b 100644 --- a/test/test_Inheritance.cpp +++ b/test/test_Inheritance.cpp @@ -128,12 +128,12 @@ TESTCASE(base_pointer_method_call) Module m = define_module("Testing"); - Object message = m.module_eval(R"EOS(notification = EmailNotification.new - notification.message)EOS"); + Object message = m.module_eval(R"(notification = EmailNotification.new + notification.message)"); ASSERT_EQUAL("Email", detail::From_Ruby().convert(message)); - message = m.module_eval(R"EOS(notification = PushNotification.new - notification.message)EOS"); + message = m.module_eval(R"(notification = PushNotification.new + notification.message)"); ASSERT_EQUAL("Push", detail::From_Ruby().convert(message)); } @@ -151,12 +151,12 @@ TESTCASE(base_pointer_function_argument) define_global_function("process_notification", &processNotification); Module m = define_module("Testing"); - Object message = m.module_eval(R"EOS(notification = EmailNotification.new - process_notification(notification))EOS"); + Object message = m.module_eval(R"(notification = EmailNotification.new + process_notification(notification))"); ASSERT_EQUAL("Email", detail::From_Ruby().convert(message)); - message = m.module_eval(R"EOS(notification = PushNotification.new - process_notification(notification))EOS"); + message = m.module_eval(R"(notification = PushNotification.new + process_notification(notification))"); ASSERT_EQUAL("Push", detail::From_Ruby().convert(message)); } @@ -175,12 +175,12 @@ TESTCASE(module_base_pointer_method_call) Module m = define_module("Testing"); - Object message = m.module_eval(R"EOS(notification = Inheritance::EmailNotification.new - notification.message)EOS"); + Object message = m.module_eval(R"(notification = Inheritance::EmailNotification.new + notification.message)"); ASSERT_EQUAL("Email", detail::From_Ruby().convert(message)); - message = m.module_eval(R"EOS(notification = Inheritance::PushNotification.new - notification.message)EOS"); + message = m.module_eval(R"(notification = Inheritance::PushNotification.new + notification.message)"); ASSERT_EQUAL("Push", detail::From_Ruby().convert(message)); } @@ -219,8 +219,8 @@ TESTCASE(base_pointer_constructor) Module m = define_module("Testing"); - Object result = m.module_eval(R"EOS(notification = PushNotification.new + Object result = m.module_eval(R"(notification = PushNotification.new processor = Processor.new(notification) - processor.process)EOS"); + processor.process)"); ASSERT_EQUAL("Push", detail::From_Ruby().convert(result)); } \ No newline at end of file From ac0a1911ceef8bd9311b0b204b05bf138967297f Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 19:16:42 -0800 Subject: [PATCH 7/8] Formatting. --- test/test_Keep_Alive_No_Wrapper.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_Keep_Alive_No_Wrapper.cpp b/test/test_Keep_Alive_No_Wrapper.cpp index 2e7052ec..3005f16a 100644 --- a/test/test_Keep_Alive_No_Wrapper.cpp +++ b/test/test_Keep_Alive_No_Wrapper.cpp @@ -75,11 +75,15 @@ TESTCASE(test_keep_alive_no_wrapper) Module m = define_module("TestingModule"); Object zoo = m.module_eval("@zoo = Zoo.new"); + std::string code = R"(@zoo.get_pets.each do |pet| + puts pet.get_name + end)"; + // get_pets returns an Array (builtin type) so Return().keepAlive() - // shall result in std::runtime_error + // should result in std::runtime_error ASSERT_EXCEPTION_CHECK( Exception, - m.module_eval("@zoo.get_pets.each do |pet| puts pet.name; end"), + m.module_eval(code), ASSERT_EQUAL("wrong argument type Array (expected wrapped C++ object)", ex.what()) ); From dd11ab014283148e91ca70f1dc2b3b692b3b87ea Mon Sep 17 00:00:00 2001 From: cfis Date: Tue, 30 Dec 2025 20:57:19 -0800 Subject: [PATCH 8/8] Reimplement smart pointers. Instead of hiding them, always make them visible to Ruby code. But allow them to be passed to methods that take T* and also forward method calls to the wrapped T to make use more natural. --- build_rice.bat | 4 + docs/architecture/smart_pointers.md | 414 ++++++++++++++++++++++++++++ docs/changelog.md | 304 +------------------- docs/stl/shared_ptr.md | 148 ++++++++++ docs/stl/smart_pointers.md | 236 ---------------- docs/stl/unique_ptr.md | 144 ++++++++++ mkdocs.yml | 4 +- rice/Data_Type.ipp | 2 +- rice/cpp_api/Class.hpp | 5 + rice/cpp_api/Class.ipp | 5 + rice/detail/Forwards.hpp | 18 ++ rice/detail/Forwards.ipp | 56 ++++ rice/detail/Type.ipp | 4 + rice/detail/Wrapper.hpp | 23 +- rice/detail/Wrapper.ipp | 115 +++++--- rice/rice.hpp | 3 + rice/stl/shared_ptr.hpp | 16 ++ rice/stl/shared_ptr.ipp | 99 ++++--- rice/stl/unique_ptr.hpp | 12 +- rice/stl/unique_ptr.ipp | 204 ++++++-------- test/test_Stl_SharedPtr.cpp | 205 +++++++++++--- test/test_Stl_UniquePtr.cpp | 51 +++- 22 files changed, 1272 insertions(+), 800 deletions(-) create mode 100644 build_rice.bat create mode 100644 docs/architecture/smart_pointers.md mode change 100644 => 120000 docs/changelog.md create mode 100644 docs/stl/shared_ptr.md delete mode 100644 docs/stl/smart_pointers.md create mode 100644 docs/stl/unique_ptr.md create mode 100644 rice/detail/Forwards.hpp create mode 100644 rice/detail/Forwards.ipp diff --git a/build_rice.bat b/build_rice.bat new file mode 100644 index 00000000..f4cd7c1c --- /dev/null +++ b/build_rice.bat @@ -0,0 +1,4 @@ +@echo off +call "C:\Program Files\Microsoft Visual Studio\18\Insiders\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1 +cd /d C:\Source\rice +cmake --build --preset msvc-debug diff --git a/docs/architecture/smart_pointers.md b/docs/architecture/smart_pointers.md new file mode 100644 index 00000000..9e9c3899 --- /dev/null +++ b/docs/architecture/smart_pointers.md @@ -0,0 +1,414 @@ +# Smart Pointer Architecture + +This document describes how Rice implements smart pointer support and how to add support for custom smart pointer types. + +## Overview + +Rice's smart pointer support involves three key components: + +1. **Wrapper specialization** - A `Wrapper>` class that stores the smart pointer and provides access to both the smart pointer and its managed object +2. **Type verification** - A `Type>` specialization that registers the smart pointer type with Rice +3. **Ruby class definition** - A function that creates the Ruby class and exposes methods + +## Wrapper Specialization + +The `Wrapper` class is responsible for storing C++ objects that are wrapped by Ruby. For smart pointers, you need to specialize `Wrapper` to handle the dual nature of smart pointers: they are both a container and provide access to a contained object. + +### Declaration + +```cpp +namespace Rice::detail +{ + template + class Wrapper> : public WrapperBase + { + public: + Wrapper(rb_data_type_t* rb_data_type, const my_smart_ptr& data); + ~Wrapper(); + void* get(rb_data_type_t* requestedType) override; + + private: + my_smart_ptr data_; + rb_data_type_t* inner_rb_data_type_; + }; +} +``` + +Key points: + +- Inherit from `WrapperBase` +- Store the smart pointer in `data_` +- Store the `rb_data_type_t*` for the inner type `T` in `inner_rb_data_type_` + +### Constructor + +The constructor must: + +1. Call the `WrapperBase` constructor with the `rb_data_type` +2. Store the smart pointer +3. Look up the `rb_data_type_t*` for the inner type + +```cpp +template +Wrapper>::Wrapper(rb_data_type_t* rb_data_type, const my_smart_ptr& data) + : WrapperBase(rb_data_type), data_(data) +{ + using Intrinsic_T = intrinsic_type; + + if constexpr (std::is_fundamental_v) + { + inner_rb_data_type_ = Data_Type>::ruby_data_type(); + } + else + { + inner_rb_data_type_ = Data_Type::ruby_data_type(); + } +} +``` + +### Destructor + +The destructor must remove the instance from Rice's instance registry: + +```cpp +template +Wrapper>::~Wrapper() +{ + Registries::instance.instances.remove(this->get(this->rb_data_type_)); +} +``` + +### get() Method + +The `get()` method is the heart of smart pointer support. It examines the `requestedType` and returns either: + +- A pointer to the smart pointer itself (when the caller wants the smart pointer) +- The raw pointer to the managed object (when the caller wants `T*`) + +```cpp +template +void* Wrapper>::get(rb_data_type_t* requestedType) +{ + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + // Caller wants the smart pointer itself + return &this->data_; + } + else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) + { + // Caller wants the managed object + return this->data_.get(); + } + else + { + throw Exception(rb_eTypeError, "wrong argument type (expected %s)", + requestedType->wrap_struct_name); + } +} +``` + +This allows Rice to automatically pass either the smart pointer or the raw pointer to C++ methods based on their signature. + +## Type Specialization + +The `Type` specialization tells Rice how to verify and register the smart pointer type: + +```cpp +namespace Rice::detail +{ + template + struct Type> + { + static bool verify() + { + // First verify the inner type. + // Note: The is_fundamental check is only needed if your smart pointer + // supports fundamental types (int, double, etc.) + bool result = true; + if constexpr (std::is_fundamental_v) + { + result = result && Type>::verify(); + } + else + { + result = result && Type::verify(); + } + + // Then register the smart pointer type + if (result) + { + define_my_smart_ptr(); + } + + return result; + } + }; +} +``` + +## Ruby Class Definition + +Create a function to define the Ruby class with appropriate methods: + +```cpp +template +Data_Type> define_my_smart_ptr(std::string klassName) +{ + using SmartPtr_T = my_smart_ptr; + using Data_Type_T = Data_Type; + + // Generate class name if not provided + if (klassName.empty()) + { + detail::TypeMapper typeMapper; + klassName = typeMapper.rubyName(); + } + + // Check if already defined (use your own module, not Std) + Module rb_mModule = define_module("YourModule"); + if (Data_Type_T::check_defined(klassName, rb_mModule)) + { + return Data_Type_T(); + } + + // Define the Ruby class with methods appropriate for your smart pointer + Identifier id(klassName); + Data_Type_T result = define_class_under>(rb_mModule, id). + define_method("empty?", &SmartPtr_T::operator bool). + define_method("get", &SmartPtr_T::get); + + // Setup method forwarding to the managed type + if constexpr (!std::is_void_v && !std::is_fundamental_v) + { + detail::define_forwarding(result.klass(), Data_Type::klass()); + } + + return result; +} +``` + +## Method and Attribute Forwarding + +Rice uses Ruby's [`Forwardable`](https://ruby-doc.org/stdlib/libdoc/forwardable/rdoc/Forwardable.html) module to forward method and attribute calls from the smart pointer wrapper to the managed object. This is implemented in `detail::define_forwarding()`. + +To avoid conflicts, methods or attributes that are already defined on the wrapper class are not forwarded. For example, if both the smart pointer wrapper and the managed type define a `swap` method, only the wrapper's version is directly accessible. To call the managed object's version, use `ptr.get.swap`. + +```cpp +inline void define_forwarding(VALUE wrapper_klass, VALUE wrapped_klass) +{ + protect(rb_require, "forwardable"); + Object forwardable = Object(rb_cObject).const_get("Forwardable"); + Object(wrapper_klass).extend(forwardable.value()); + + // Get wrapper class's method and attribute names to avoid conflicts + std::set wrapperMethodSet; + std::vector wrapperMethods = Registries::instance.natives.lookup(wrapper_klass, NativeKind::Method); + wrapperMethodSet.insert(wrapperMethods.begin(), wrapperMethods.end()); + std::vector wrapperReaders = Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeReader); + wrapperMethodSet.insert(wrapperReaders.begin(), wrapperReaders.end()); + std::vector wrapperWriters = Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeWriter); + wrapperMethodSet.insert(wrapperWriters.begin(), wrapperWriters.end()); + + // Get wrapped class's method and attribute names, including ancestor classes + std::set wrappedMethodSet; + Class klass(wrapped_klass); + while (klass.value() != rb_cObject && klass.value() != Qnil) + { + std::vector methods = Registries::instance.natives.lookup(klass.value(), NativeKind::Method); + wrappedMethodSet.insert(methods.begin(), methods.end()); + + std::vector readers = Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeReader); + wrappedMethodSet.insert(readers.begin(), readers.end()); + + std::vector writers = Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeWriter); + wrappedMethodSet.insert(writers.begin(), writers.end()); + + klass = klass.superclass(); + } + + // Build arguments: [:get, :method1, :method2, ...] + // Skip methods already defined on the wrapper class + Array args; + args.push(Symbol("get")); + for (const std::string& method : wrappedMethodSet) + { + if (wrapperMethodSet.find(method) == wrapperMethodSet.end()) + { + args.push(Symbol(method)); + } + } + + // Call def_delegators + Object(wrapper_klass).vcall("def_delegators", args); +} +``` + +## From_Ruby Specialization (Optional) + +If your smart pointer has special copy/move semantics (like move-only types), you may need a `From_Ruby` specialization: + +```cpp +template +class From_Ruby> +{ +public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } + + double is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_DATA: + return Convertible::Exact; + default: + return Convertible::None; + } + } + + my_smart_ptr convert(VALUE value) + { + my_smart_ptr* result = detail::unwrap>( + value, + Data_Type>::ruby_data_type(), + this->arg_ && this->arg_->isOwner()); + return std::move(*result); // Use std::move for move-only types + } + +private: + Arg* arg_ = nullptr; +}; +``` + +## Summary + +To add support for a custom smart pointer: + +1. Specialize `Wrapper>` with a `get()` method that can return either the smart pointer or the managed object +2. Specialize `Type>` to verify and register the type +3. Create a `define_your_smart_ptr()` function to define the Ruby class +4. Optionally specialize `From_Ruby` if your smart pointer has special copy/move semantics + +## Complete Code + +Here is all the code together for easy copy-paste: + +```cpp +namespace Rice::detail +{ + // Wrapper specialization + template + class Wrapper> : public WrapperBase + { + public: + Wrapper(rb_data_type_t* rb_data_type, const my_smart_ptr& data) + : WrapperBase(rb_data_type), data_(data) + { + using Intrinsic_T = intrinsic_type; + + // Note: The is_fundamental check is only needed if your smart pointer + // supports fundamental types (int, double, etc.) + if constexpr (std::is_fundamental_v) + { + inner_rb_data_type_ = Data_Type>::ruby_data_type(); + } + else + { + inner_rb_data_type_ = Data_Type::ruby_data_type(); + } + } + + ~Wrapper() + { + Registries::instance.instances.remove(this->get(this->rb_data_type_)); + } + + void* get(rb_data_type_t* requestedType) override + { + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return &this->data_; + } + else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) + { + return this->data_.get(); + } + else + { + throw Exception(rb_eTypeError, "wrong argument type (expected %s)", + requestedType->wrap_struct_name); + } + } + + private: + my_smart_ptr data_; + rb_data_type_t* inner_rb_data_type_; + }; + + // Type specialization + template + struct Type> + { + static bool verify() + { + // Note: The is_fundamental check is only needed if your smart pointer + // supports fundamental types (int, double, etc.) + bool result = true; + if constexpr (std::is_fundamental_v) + { + result = result && Type>::verify(); + } + else + { + result = result && Type::verify(); + } + + if (result) + { + define_my_smart_ptr(); + } + + return result; + } + }; +} + +namespace Rice +{ + // Ruby class definition + template + Data_Type> define_my_smart_ptr(std::string klassName = "") + { + using SmartPtr_T = my_smart_ptr; + using Data_Type_T = Data_Type; + + if (klassName.empty()) + { + detail::TypeMapper typeMapper; + klassName = typeMapper.rubyName(); + } + + Module rb_mModule = define_module("YourModule"); + if (Data_Type_T::check_defined(klassName, rb_mModule)) + { + return Data_Type_T(); + } + + // Define the Ruby class with methods appropriate for your smart pointer + Identifier id(klassName); + Data_Type_T result = define_class_under>(rb_mModule, id). + define_method("empty?", &SmartPtr_T::operator bool). + define_method("get", &SmartPtr_T::get); + + if constexpr (!std::is_void_v && !std::is_fundamental_v) + { + detail::define_forwarding(result.klass(), Data_Type::klass()); + } + + return result; + } +} +``` diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 3d5a78a1..00000000 --- a/docs/changelog.md +++ /dev/null @@ -1,303 +0,0 @@ -# Changelog - -## 4.8.0 (2025-11-28) -The release focuses on compilation times and library sizes. Compilation times are approximately 2x faster than version 4.7 and a bit faster than 4.6. Library sizes are about 30% smaller. - -However, these updates required some breaking changes, which include: - -* `Return().isBuffer()` is replaced by `ReturnBuffer()` -* `Arg("").isBuffer()` is replaced by `ArgBuffer("")` -* `Function().noGVL()` is replaced by `NoGvL()` - -See the pointer documentation for information on how to use them. - -## 4.7.1 (2025-10-28) -Updates: -* Update overload resolution to take into account function arguments that are tagged as buffers via Arg("").setBuffer(). -* Make second parameter optional for Array#push and update docs -* Remove ostruct runtime dependency - -## 4.7.0 (2025-10-22) -Updates: -* Refactor Native wrappers - functions, methods, attributes and procs - to enable introspection API -* Introduce Pointer class to wrap pointers to fundamental types and arrays. -* Add new methods Arg#setBuffer and Return#setBuffer to indicate that a C++ pointer references an array of objects versus a single object -* Add a new Introspection API that exposes Rice internals to Ruby -* Using the Introspection API, add support for generating RBS files for extensions. See bin/rice-rbs.rb -* Using the Introspection API, add support for generating Markdown documentation for extensions. See bin/rice-doc.rb -* Don't create attribute writers for const attributes -* Support attribute setters for Enums -* Support wrapping std::vector> -* Update Array#push to not always copy C++ instances. This lays the foundation for extracting references and pointers from tuples, variants and optionals. -* Add very basic support for creating std::filesystem::path instances -* Remove toy samples and test libraries. These will be replaced by a new gem that wraps the BitMapPlusPlus library (https://github.com/baderouaich/BitmapPlusPlus) -* Add support for std::runtime_error since some libraries use that as a base exception class (thus when Rice wraps custom exceptions it also needs to wrap the base class) -* Improve std::vector indexing to more closely match Ruby for negative index values -* Correctly encode UTF8 Ruby class names in exception messages -* Add support for disabling Ruby's global interpreter lock (GIL) when calling native functions - -Breaking Changes: -* Custom implementations of From_Ruby must include a custom constructor: - ``` - explicit From_Ruby(Arg* arg) - ``` -* Custom implementations of To_Ruby must include a custom constructor: - ``` - explicit To_Ruby(Return* returnInfo) - ``` -* You can no longer pass a Buffer to an API that takes a pointer. Instead use Buffer#data or Buffer::release -* The Rice_Init method has been removed. -* Array#push requires a second argument. - -## 4.6.1 (2025-06-25) -* Improve attribute handling. Correctly deal with non-copyable/assignable attributes and return references instead of copies of objects -* Improve Buffer implementation to deal with 4 cases: - - array of fundamental types (int*) - - array of pointers fundamental types (char**) - - array of objects (someInstance*) - - array of pointers of objects (someInstance**) -* Implement Buffer#to_s -* Fix header check on Ubuntu 22.04 - -## 4.6.0 (2025-06-09) -Rice 4.6 is a major release that adds significant new functionality based on wrapping the OpenCV library, including: - -* Add a new Buffer class to provide Ruby API support for pointers sent to or returned by C++ -* Support C style out parameters -* Support C style arrays -* Rewrite keyword arguments -* Rewrite default value handling allowing the removal of a lot of boilerplate -* Add support for std::multimap -* Add support for std::set -* Add support for std::tuple -* Add support for smart pointers (shared_ptr, unique_ptr) to fundamental types (void, int, etc) -* Improve std::variant support -* Update is_convertible documentation -* Fix missing version.rb file in gemspec -* Add C++ preprocessor defines for Rice version -* Include Rice::VERSION in published gem -* Moved auto-generated C++ STL classes (std::vector, std::map, etc.) from the module Rice::Std module to Std module -* Make Rice const aware to improve overload method support -* Improve error messages when Rice cannot determine what overloaded method to call -* Improve handling of unbound Data_Type instances to avoid crashes when initializing global static variables of type Data_Type -* Make Enums more useful by adding coerce method to enable stringing together bitwise operators - for example Season::Winter | Season::Spring | Season::Summer. - -## 4.5 (2025-02-09) -Rice 4.5 is a major release that adds significant new functionality, including: - -* Support method overloading -* Support constructor overloading -* Support rvalues -* Support using keyword arguments in Ruby to call C++ methods -* Support C style callbacks, including adding a new define_callback method -* Support wrapping C/C++ functions as Ruby procs -* Support calling methods that take pointers -* Add Data_Type#define method to more easily support C++ template classes -* Adds #define_constant method -* Be more flexible on type verification by not throwing errors until all classes/methods have been defined and also allow a error message to be printed instead of thrown -* Add ability to transfer ownership of Ruby created objects to C++ (useful for sending wrapped pointers to APIs that take smart pointers) -* Add support for *char -* Add support for **char -* Improve C++ exception handling when called from Ruby -* Improve handling of Ruby exceptions -* Update std::variant to support references -* Split NativeAttribute support to NativeAttributeGet and NativeAttributeSet -* Create base class for Native classes (NativeFunction, NativeIterator, NativeAttributeGet, NativeAttributeSet - make the type registry easier to deal with (thus using type erasure like the Wrapper classes do). -* Support Ruby 3.4 -* Lots of documentation additions and updates -* Updated FindRuby to support rbenv (this change is also merged upstream to CMake) - -This release also improves STL support by adding: - -* std::exception -* std::exception_ptr -* std::monostage -* std::shared_ptr -* std::type_index -* std::type_info -* std::vector - -Please see the migration guide for updating your bindings for version 4.5 - -## 4.3.3 (2024-10-19) - -* Fix complication issue on Ubuntu 20.04 and GCC 9. - -## 4.3.2 (2024-10-18) - -* Improve NativeRegistry to reduce possible hash collisions and weird "bad any cast" errors. - -## 4.3.1 (2024-3-16) - -* Update links and related references to the new repo and docs location: ruby-rice.github.io. - -## 4.3 (2024-2-25) - -* Add support for STL containers that contain pointers -* Add support for std::string_view -* Fix handling of std::shared_ptr that resulted in moving them instead of copying them -* Fix container iteration so elements are passed by reference and not copied -* Avoid unnecessary copies when creating Rice::Identifiers and Rice::Symbols - -## 4.2.1 (2024-1-20) - -* Support systems who use `#include ` over `#include`. See [#197](https://github.com/jasonroelofs/rice/issues/197) and [#201](https://github.com/jasonroelofs/rice/pull/201) - -## 4.2 (2024-1-10) - -* Support Ruby 3.3.0. -* Split Object.call to an explicit Object.call_kw for calling methods expecting keyword arguments. -* Previously, if a wrapper used `keepAlive` on an argument or return value that was itself a Rice type, calling said method would segfault. We've now added an explicit exception to be thrown in this case, prevending the segfault and providing guidance on what was wrong and how to fix it. See [#193](https://github.com/jasonroelofs/rice/pull/193) and [#194](https://github.com/jasonroelofs/rice/pull/194) -* Fix wrapping of std::shared_ptr to properly take default arguments into account. - -## 4.1 (2023-4-21) - -Rice 4.1 builds on the 4.0 release and has a number of improvements that both polish Rice and extend its functionality. However, there are three incompatibilities to know about: - -* Exception handlers are now registered globally versus per module. This requires updating code that calls Class#add_handler to use register_handler instead. -* Rename Arg#isValue to Arg#setValue and then Arg#getIsValue to Arg#isValue -* Rename Return#isValue to Return#setValue and Return#getIsValue to Return#isValue - -New or improved functionality includes: - -* Add support for std::map, std::unordered_map, std::variant, std::monostate and std::reference_wrapper -* Enable calling of C++ member functions that are defined in ancestor classes -* Make it easy to wrap C++ iterators like std::vector begin and end -* Enable creating enumerators for C++ collections like std::vector and std::map -* Enable calling more Ruby API methods including those with a variable number of parameters such as rb_yield_values -* Add additional C++ to Ruby exception mappings (for example, std::system_error to SystemCallError) -* Updated documentation, including new pages for instance tracking, iterators, exceptions and newly supported STL classes -* Add support for calling Ruby methods with keywords from Rice::Object and its descendants -* Automatically translate C++ character arrays that start with colons to symbols (ie, ":mysymbol") when sending them to Ruby -* Add a constructor for Rice::Module that takes a name, to enable code like Module("Kernel") -* Fix comparison methods in Rice::Object, such as Object#is_equal, to return the correct result -* Fix various compiler warnings -* Remove deprecated APIs -* Remove support for Ruby 2.5 and 2.6 which are officially out of support -* Add support for building tests with CMake -* And lots of other fixes and code improvements - -Rice also includes experimental support for instance tracking so that Rice maps the same C++ instance to the same Ruby instance each time it is passed to Ruby. See the documentation for more information. - - -## 4.0 (2021-4-8) - -Rice 4.0 is a significant change from 3.0 and has multiple backwards-incompatible -changes. Rice 4.0 no longer requires pre-compilation and is now a header-only library, -delivered as a combined header file. - -For migrating from 3 to 4, see [the migration guide](https://ruby-rice.github.io/4.x/migration.html). - -There are a ton of changes, but some of the most important ones: - -* Header only! `#include ` -* Requires C++17 or later -* Brand new, expanded documentation -* [Built-in STL support](https://ruby-rice.github.io/4.x/stl/stl.html) -* And so much more. See the documentation for more details. - -## 3.0 (2021-1-8) - -* Now requires a compiler supporting for C++14 or later -* Drop support for Ruby 2.4. Supported versions are now 2.5 through 3.0. -* Fix build issue on macOS Big Sur -* Fix a data corruption issue with `Rice::Exception::what`. -* Move CI from Travis to GitHub Actions. Now also able to verify Windows builds! - -## 2.2.0 (2020-1-10) - -* Deprecate support for Rubies older than 2.4 -* Provide a few more built-in to_ruby/from_ruby conversions -* Fix compilation error when building under Ruby 2.7.0 - -## 2.1.3 (2019-2-28) - -* Don't lock down HAVE_CXX11 on the Rice build itself. - -## 2.1.2 (2017-11-20) - -* Fix defining custom `begin` and `end` methods on an `Iterator` - -## 2.1.1 (2020-1-10) - -* Support Ruby 2.4 -* Re-enable Rice::Enum#hash to support putting Enums in Hashes - -## 2.1.0 (2016-1-1) - -* Fix compliation issues related to g++ and Ruby 2.3.0 - To do this, I had to remove Array::to_c_array which was exposing the internals of a - Ruby RArray type to the system. This is not something that we should support going forward - as these internals are going to change. - -## 2.0.0 (2015-11-27) - -* Deprecated all versions of Ruby < 2.0 -* Removed Rice::VM. - Unsure if this class is even used anywhere and it felt strange to be - able to load up a Ruby interpreter inside of Ruby. If you need it, it's - two files that I can easily make available in a gist. -* Improve build process across architectures and future changes. - Included some extra warnings for XCode updates on Mac OS X. -* Confirmed that Rice definitely does not work on static Ruby builds, - but that seems to be more because newer Ruby versions don't have good static builds. - Thanks to @Kagetsuki for his help tracking down what's going on here. - -## 1.7.0 (2015-1-6) - -* Ruby 2.2 support - Potential breaking changes. Ruby 2.2 removed RHash as a public accessible struct - and as such I changed all of the Builtin_Objects to work directly off of RObject - instead of the specifics (RArray, RStruct, RString, etc). If you've been using these - objects directly I recommend using either the Rice API or Ruby's CAPI instead for - future compatibility. - -## 1.6.3 (2014-12-18) - -* Fix complication issue on some 64-bit *nix systems - -## 1.6.2 (2014-5-5) - -* Oops! Missed new file in the gemspec - -## 1.6.1 (2014-5-5) - -* Support C++x11 uniqe_ptr over auto_ptr -* Fix some warnings - -## 1.6.0 (2014-2-3) - -* Ruby 2.1 support -- Thanks Chai Zhenhua -* Methods and Constructors have correct method access specifiers [#57] -* Clean up some 64-bit compiler warnings - -## 1.5.3 (2013-10-14) - -* Fix signed / unsigned compiler warning with Hash#each -* Fix compilation on clang 5 (Xcode 5) - -## 1.5.2 (2013-10-5) - -* Update build system to remove deprecation warnings and allow easier building -* Fix String to work as a parameter in a wrapped method (#59) -* Update documentation a bit - -## 1.5.1 (2013-5-2) - -* Doc string fix - -## 1.5.0 (2013-5-1) - -* Ruby 2.0 compatability -* Bug fixes - -## 1.4.3 (2011-10-9) - -* Various build configuration fixes - -## 1.4.0 (2010-8-30) - -* Fully compatible with Ruby 1.9.2 -* Constructor supports default arguments -* Ability to define implicit casting through define_implicit_cast -* Fixed a few memory-related issues diff --git a/docs/changelog.md b/docs/changelog.md new file mode 120000 index 00000000..04c99a55 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/docs/stl/shared_ptr.md b/docs/stl/shared_ptr.md new file mode 100644 index 00000000..17eba48f --- /dev/null +++ b/docs/stl/shared_ptr.md @@ -0,0 +1,148 @@ +# std::shared_ptr + +Rice provides full support for `std::shared_ptr`, allowing C++ shared pointers to be used seamlessly from Ruby. + +## Ruby API + +When Rice wraps a `std::shared_ptr`, it creates a Ruby class under the `Std` module. The class exposes the following methods: + +| Method | Description | +| ------ | ----------- | +| `empty?` | Returns true if the shared pointer is null | +| `get` | Returns the managed object | +| `swap` | Swaps the managed object with another shared_ptr | +| `use_count` | Returns the number of shared_ptr instances managing the object | + +For non-void types, a constructor is also defined that takes ownership of a raw pointer. + +Additionally, for non-fundamental and non-void types, Rice sets up forwarding so that methods and attributes defined on `T` can be called directly on the shared pointer wrapper. + +Note: Methods or attributes that have the same name on both the smart pointer wrapper and the managed type (e.g., `swap`) are not forwarded. To call the managed object's version, use `ptr.get.method_name`. + +## Example + +```cpp +class MyClass +{ +public: + int flag = 0; + + void setFlag(int value) + { + this->flag = value; + } + + int getFlag() + { + return this->flag; + } +}; + +class Factory +{ +public: + std::shared_ptr share() + { + if (!instance_) + { + instance_ = std::make_shared(); + } + return instance_; + } + +private: + static inline std::shared_ptr instance_; +}; + +void setupRice() +{ + define_class("MyClass"). + define_method("flag=", &MyClass::setFlag). + define_method("flag", &MyClass::getFlag); + + define_class("Factory"). + define_constructor(Constructor()). + define_method("share", &Factory::share); +} +``` + +In Ruby: + +```ruby +factory = Factory.new +my_instance = factory.share + +# Check the shared pointer +puts my_instance.empty? # => false +puts my_instance.use_count # => 1 + +# Methods are forwarded to the managed object +my_instance.flag = 5 +puts my_instance.flag # => 5 + +# Can also access via get +puts my_instance.get.flag # => 5 +``` + +## Ownership + +When a native method returns a `std::shared_ptr` by value, Rice copies it to maintain standard shared pointer semantics. This increments the reference count, and Ruby then shares ownership with any other `std::shared_ptr` instances in C++ code. + +When the Ruby object is garbage collected, it releases its reference to the `std::shared_ptr`, decrementing the reference count. The underlying C++ object is freed only when all shared pointer instances are destroyed. + +If a method returns a `std::shared_ptr` by reference or pointer (which would be unusual), Rice's usual reference/pointer semantics apply instead. + +## Passing to C++ Methods + +Rice automatically determines whether to pass the `std::shared_ptr` itself or the underlying raw pointer based on what the C++ method expects. This happens efficiently in C++ without requiring a round-trip through Ruby. + +```cpp +// Method that takes a shared_ptr +int processShared(std::shared_ptr ptr) +{ + return ptr->getFlag(); +} + +// Method that takes a raw pointer +int processRaw(MyClass* ptr) +{ + return ptr->getFlag(); +} + +void setupRice() +{ + define_class("MyClass"). + define_method("flag", &MyClass::getFlag); + + define_global_function("process_shared", &processShared); + define_global_function("process_raw", &processRaw); +} +``` + +In Ruby, you simply pass the shared pointer wrapper to either method: + +```ruby +factory = Factory.new +my_instance = factory.share + +# Rice passes the std::shared_ptr to C++ +process_shared(my_instance) + +# Rice extracts the raw pointer and passes it to C++ +process_raw(my_instance) + +# This also works but is less efficient because it goes through Ruby +process_raw(my_instance.get) +``` + +## Type Registration + +Rice automatically registers `std::shared_ptr` types when they are used. You can also explicitly register them: + +```cpp +Rice::define_shared_ptr(); +``` + +## See Also + +To learn how to implement support for a custom smart pointer type, see [Smart Pointer Architecture](../architecture/smart_pointers.md). diff --git a/docs/stl/smart_pointers.md b/docs/stl/smart_pointers.md deleted file mode 100644 index 2d839f98..00000000 --- a/docs/stl/smart_pointers.md +++ /dev/null @@ -1,236 +0,0 @@ -# Smart Pointers - -Smart pointers are key tool in modern C++ to write memory safe code. Rice provides support for `std::unique_ptr` and `std::shared_ptr`. Rice can also be easily extended to support custom smart pointer types. - -## std::unique_ptr - -When a native method returns a `std::unique_ptr`, Rice copies it via its move constructor. Therefore, Rice takes ownership of the `std::unique_ptr`, which in turns owns its underlying object. - -Use of the `std::unique_ptr` is transparent to Ruby code. As far a Ruby is concerned, its wrapping the type managed by the unique pointer, thus `std::unique_ptr::element_type`. As a result, there is not a Ruby visible API to the `std::unique_ptr` itself. - -Rice supports `std::unique_ptr` out of the box - there is no additional work you have to do except include the "rice/stl.hpp" header. Let's take a look at an example: - -```cpp -class MyClass -{ -public: - int flag = 0; - -public: - void setFlag(int value) - { - this->flag = value; - } -}; - -class Factory -{ -public: - std::unique_ptr transfer() - { - return std::make_unique(); - } -}; - -int extractFlagUniquePtrRef(std::unique_ptr& myClass) -{ - return myClass->flag; -} - -void setupRice() -{ - define_class("MyClass"). - define_method("set_flag", &MyClass::setFlag); - - define_class("Factory"). - define_constructor(Constructor()). - define_method("transfer", &Factory::transfer); - - define_global_function("extract_flag_unique_ptr_ref", &extractFlagUniquePtrRef); -} -``` - -And in Ruby: - -```ruby -factory = Factory.new -my_instance = factor.transfer -my_instance.set_flag(5) -flag = extract_flag_unique_ptr_ref(my_instance) -``` - -When `myInstance` goes out of scope and is garbage collected, it will free the `std::unique_ptr` it wraps which will in turn free the C++ MyClass instance that it manages. - -Note Rice does not support transferring ownership of the `std::unique_ptr` back to C++ by passing it as a function parameter to a native function. In other words, once a `std::unique_ptr` is transferred to Ruby it will be freed by Ruby. You can, however, pass back a reference as shown in the example above. Note that passing a reference to `std::unique_ptr` does *not* not transer its ownership. - -## std::shared_ptr - -When a native method returns a `std::shared_ptr`, Rice copies it via its move constructor and therefore owns one instance of the shared_pointer. Of course it is likely there are other copies of the `std::shared_ptr` owned by other objects. - -Use of the `std::shared_ptr` is transparent to Ruby code. As far a Ruby is concerned, its wrapping the type managed by the shared pointer, thus `std::shared_ptr::element_type`. As a result, there is not a Ruby visible API to the `std::unique_ptr` itself. - -Rice supports `std::shared_ptr` out of the box - there is no additional work you have to do except include the "rice/stl.hpp" header. Let's take a look at an example: - -```cpp -class MyClass -{ -public: - int flag = 0; - -public: - void setFlag(int value) - { - this->flag = value; - } -}; - -class Factory -{ -public: - std::shared_ptr share() - { - if (!instance_) - { - instance_ = std::make_shared(); - } - return instance_; - } - -public: - static inline std::shared_ptr instance_; -}; - -int extractFlagSharedPtr(std::shared_ptr myClass) -{ - return myClass->flag; -} - -int extractFlagSharedPtrRef(std::shared_ptr& myClass) -{ - return myClass->flag; -} - -void setupRice() -{ - embed_ruby(); - - define_class("MyClass"). - define_method("set_flag", &MyClass::setFlag); - - define_class("Factory"). - define_constructor(Constructor()). - define_method("share", &Factory::share); - - define_global_function("extract_flag_shared_ptr", &extractFlagSharedPtr); - define_global_function("extract_flag_shared_ptr_ref", &extractFlagSharedPtrRef); -} -``` - -And in Ruby: - -```ruby -factory = Factory.new -my_instance = factor.share -my_instance.set_flag(5) -flag = extract_flag_shared_ptr(my_instance) -flag = extract_flag_shared_ptr_ref(my_instance) -``` - -When `myInstance` goes out of scope and is garbage collected, it will free the `std::shared_ptr` it wraps. That may or may not free the underlying C++ MyClass instance depending if there are other `std::smart_pointer` instances managing it. - -Unlike `std::unique_ptr`, you can pass a copy of a `std::shared_ptr` back to native code via a function parameter. However, Ruby will always maintain one copy of the shared pointer until the wrapper Ruby object is freed. - -## Custom Smart Pointer - -It is possible to extend Rice to support additional smart pointer types. Start by looking at `stl/smart_ptr.hpp`. It defines the following template class that is used to store smart pointers: - -```cpp -namespace Rice::detail -{ - template