rice 4.8.0 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -1
  3. data/CMakePresets.json +77 -50
  4. data/FindRuby.cmake +1 -1
  5. data/bin/rice-doc.rb +2 -0
  6. data/include/rice/api.hpp +14 -1
  7. data/include/rice/rice.hpp +351 -132
  8. data/include/rice/stl.hpp +319 -256
  9. data/lib/rice/doc/config.rb +57 -57
  10. data/lib/rice/doc/cpp_reference.rb +158 -158
  11. data/lib/rice/doc/doxygen.rb +289 -289
  12. data/lib/rice/doc/mkdocs.rb +332 -332
  13. data/lib/rice/doc/rice.rb +48 -47
  14. data/lib/rice/doc/ruby.rb +26 -26
  15. data/lib/rice/native.rb +15 -15
  16. data/lib/rice/native_registry.rb +12 -17
  17. data/lib/rice/parameter.rb +5 -5
  18. data/lib/rice/rbs.rb +72 -72
  19. data/lib/rice/version.rb +1 -1
  20. data/lib/rubygems/builder.rb +9 -9
  21. data/lib/rubygems_plugin.rb +8 -8
  22. data/rice/Data_Type.ipp +12 -7
  23. data/rice/cpp_api/Class.hpp +5 -0
  24. data/rice/cpp_api/Class.ipp +5 -0
  25. data/rice/cpp_api/Object.hpp +6 -0
  26. data/rice/cpp_api/Object.ipp +5 -0
  27. data/rice/detail/Forwards.hpp +18 -0
  28. data/rice/detail/Forwards.ipp +60 -0
  29. data/rice/detail/Native.ipp +2 -4
  30. data/rice/detail/NativeAttributeGet.ipp +1 -1
  31. data/rice/detail/NativeAttributeSet.hpp +5 -3
  32. data/rice/detail/NativeAttributeSet.ipp +41 -33
  33. data/rice/detail/NativeMethod.ipp +25 -22
  34. data/rice/detail/NativeRegistry.hpp +4 -2
  35. data/rice/detail/NativeRegistry.ipp +42 -9
  36. data/rice/detail/Parameter.ipp +3 -4
  37. data/rice/detail/Type.ipp +4 -0
  38. data/rice/detail/Wrapper.hpp +17 -12
  39. data/rice/detail/Wrapper.ipp +95 -36
  40. data/rice/rice.hpp +3 -0
  41. data/rice/rice_api/NativeRegistry.ipp +14 -1
  42. data/rice/stl/exception.ipp +1 -1
  43. data/rice/stl/filesystem.ipp +1 -1
  44. data/rice/stl/map.ipp +13 -11
  45. data/rice/stl/multimap.ipp +13 -11
  46. data/rice/stl/pair.ipp +14 -8
  47. data/rice/stl/set.ipp +16 -16
  48. data/rice/stl/shared_ptr.hpp +16 -0
  49. data/rice/stl/shared_ptr.ipp +74 -37
  50. data/rice/stl/type_index.ipp +1 -1
  51. data/rice/stl/unique_ptr.hpp +9 -3
  52. data/rice/stl/unique_ptr.ipp +80 -124
  53. data/rice/stl/unordered_map.ipp +14 -12
  54. data/rice/stl/vector.ipp +67 -31
  55. data/test/test_Attribute.cpp +72 -0
  56. data/test/test_Callback.cpp +3 -0
  57. data/test/test_Inheritance.cpp +14 -14
  58. data/test/test_Keep_Alive_No_Wrapper.cpp +6 -2
  59. data/test/test_Stl_Map.cpp +46 -0
  60. data/test/test_Stl_Multimap.cpp +46 -0
  61. data/test/test_Stl_Set.cpp +34 -0
  62. data/test/test_Stl_SharedPtr.cpp +160 -45
  63. data/test/test_Stl_UniquePtr.cpp +48 -3
  64. data/test/test_Stl_Unordered_Map.cpp +46 -0
  65. data/test/test_Stl_Variant.cpp +10 -14
  66. data/test/test_Stl_Vector.cpp +140 -13
  67. data/test/test_Tracking.cpp +3 -0
  68. metadata +3 -1
data/rice/stl/vector.ipp CHANGED
@@ -58,13 +58,13 @@ namespace Rice
58
58
 
59
59
  if constexpr (std::is_copy_constructible_v<Value_T>)
60
60
  {
61
- klass_.define_constructor(Constructor<T, const T&>())
62
- .define_constructor(Constructor<T, Size_T, const Parameter_T>());
61
+ klass_.define_constructor(Constructor<T, const T&>(), Arg("other"))
62
+ .define_constructor(Constructor<T, Size_T, const Parameter_T>(), Arg("count"), Arg("value"));
63
63
  }
64
64
 
65
65
  if constexpr (std::is_default_constructible_v<Value_T>)
66
66
  {
67
- klass_.define_constructor(Constructor<T, Size_T>());
67
+ klass_.define_constructor(Constructor<T, Size_T>(), Arg("count"));
68
68
  }
69
69
 
70
70
  // Allow creation of a vector from a Ruby Array
@@ -72,18 +72,20 @@ namespace Rice
72
72
  {
73
73
  // Create a new vector from the array
74
74
  T* data = new T();
75
- data->reserve(array.size());
76
75
 
76
+ // Wrap the vector
77
+ detail::Wrapper<T*>* wrapper = detail::wrapConstructed<T>(self, Data_Type<T>::ruby_data_type(), data);
78
+
79
+ // Now populate the vector
77
80
  detail::From_Ruby<Value_T> fromRuby;
81
+ data->reserve(array.size());
78
82
 
79
83
  for (long i = 0; i < array.size(); i++)
80
84
  {
81
85
  VALUE element = detail::protect(rb_ary_entry, array, i);
82
86
  data->push_back(fromRuby.convert(element));
87
+ wrapper->addKeepAlive(element);
83
88
  }
84
-
85
- // Wrap the vector
86
- detail::wrapConstructed<T>(self, Data_Type<T>::ruby_data_type(), data);
87
89
  });
88
90
  }
89
91
 
@@ -91,18 +93,18 @@ namespace Rice
91
93
  {
92
94
  if constexpr (std::is_default_constructible_v<Value_T> && std::is_same_v<Value_T, bool>)
93
95
  {
94
- klass_.define_method("resize", static_cast<void (T::*)(const size_t, bool)>(&T::resize));
96
+ klass_.define_method("resize", static_cast<void (T::*)(const size_t, bool)>(&T::resize), Arg("count"), Arg("value"));
95
97
  }
96
98
  else if constexpr (std::is_default_constructible_v<Value_T>)
97
99
  {
98
- klass_.define_method("resize", static_cast<void (T::*)(const size_t)>(&T::resize));
100
+ klass_.define_method("resize", static_cast<void (T::*)(const size_t)>(&T::resize), Arg("count"));
99
101
  }
100
102
  else
101
103
  {
102
104
  klass_.define_method("resize", [](const T&, Size_T)
103
105
  {
104
106
  // Do nothing
105
- });
107
+ }, Arg("count"));
106
108
  }
107
109
  }
108
110
 
@@ -111,13 +113,11 @@ namespace Rice
111
113
  klass_.define_method("empty?", &T::empty)
112
114
  .define_method("capacity", &T::capacity)
113
115
  .define_method("max_size", &T::max_size)
114
- .define_method("reserve", &T::reserve)
116
+ .define_method("reserve", &T::reserve, Arg("new_cap"))
115
117
  .define_method("size", &T::size);
116
-
118
+
117
119
  rb_define_alias(klass_, "count", "size");
118
120
  rb_define_alias(klass_, "length", "size");
119
- //detail::protect(rb_define_alias, klass_, "count", "size");
120
- //detail::protect(rb_define_alias, klass_, "length", "size");
121
121
  }
122
122
 
123
123
  void define_access_methods()
@@ -158,7 +158,7 @@ namespace Rice
158
158
  {
159
159
  return vector[index];
160
160
  }
161
- })
161
+ }, Arg("pos"))
162
162
  .template define_method<Value_T*(T::*)()>("data", &T::data, ReturnBuffer());
163
163
  }
164
164
  else
@@ -197,7 +197,7 @@ namespace Rice
197
197
  {
198
198
  return vector[index];
199
199
  }
200
- });
200
+ }, Arg("pos"));
201
201
  }
202
202
 
203
203
  klass_.define_method("[]", [this](T& vector, Difference_T start, Difference_T length) -> VALUE
@@ -230,7 +230,7 @@ namespace Rice
230
230
 
231
231
  return result;
232
232
  }
233
- }, Return().setValue());
233
+ }, Arg("start"), Arg("length"), Return().setValue());
234
234
 
235
235
  rb_define_alias(klass_, "at", "[]");
236
236
  }
@@ -243,7 +243,7 @@ namespace Rice
243
243
  klass_.define_method("==", [](T& vector, T& other)->bool
244
244
  {
245
245
  return vector == other;
246
- })
246
+ }, Arg("other"))
247
247
  .define_method("delete", [](T& vector, Parameter_T element) -> std::optional<Value_T>
248
248
  {
249
249
  auto iter = std::find(vector.begin(), vector.end(), element);
@@ -261,11 +261,11 @@ namespace Rice
261
261
  {
262
262
  return std::nullopt;
263
263
  }
264
- })
264
+ }, Arg("value"))
265
265
  .define_method("include?", [](T& vector, Parameter_T element)
266
266
  {
267
267
  return std::find(vector.begin(), vector.end(), element) != vector.end();
268
- })
268
+ }, Arg("value"))
269
269
  .define_method("index", [](T& vector, Parameter_T element) -> std::optional<Difference_T>
270
270
  {
271
271
  auto iter = std::find(vector.begin(), vector.end(), element);
@@ -277,7 +277,7 @@ namespace Rice
277
277
  {
278
278
  return iter - vector.begin();
279
279
  }
280
- });
280
+ }, Arg("value"));
281
281
  rb_define_alias(klass_, "eql?", "==");
282
282
  }
283
283
  else
@@ -285,15 +285,15 @@ namespace Rice
285
285
  klass_.define_method("delete", [](T&, Parameter_T) -> std::optional<Value_T>
286
286
  {
287
287
  return std::nullopt;
288
- })
288
+ }, Arg("value"))
289
289
  .define_method("include?", [](const T&, Parameter_T)
290
290
  {
291
291
  return false;
292
- })
292
+ }, Arg("value"))
293
293
  .define_method("index", [](const T&, Parameter_T) -> std::optional<Difference_T>
294
294
  {
295
295
  return std::nullopt;
296
- });
296
+ }, Arg("value"));
297
297
  }
298
298
  }
299
299
 
@@ -315,7 +315,7 @@ namespace Rice
315
315
  vector.erase(iter);
316
316
  return std::nullopt;
317
317
  }
318
- })
318
+ }, Arg("pos"))
319
319
  .define_method("insert", [this](T& vector, Difference_T index, Parameter_T element) -> T&
320
320
  {
321
321
  size_t normalized = normalizeIndex(vector.size(), index, true);
@@ -329,7 +329,7 @@ namespace Rice
329
329
  auto iter = vector.begin() + normalized;
330
330
  vector.insert(iter, std::move(element));
331
331
  return vector;
332
- })
332
+ }, Arg("pos"), Arg("value").keepAlive())
333
333
  .define_method("pop", [](T& vector) -> std::optional<Value_T>
334
334
  {
335
335
  if constexpr (!std::is_copy_assignable_v<Value_T>)
@@ -352,13 +352,13 @@ namespace Rice
352
352
  {
353
353
  vector.push_back(std::move(element));
354
354
  return vector;
355
- })
355
+ }, Arg("value").keepAlive())
356
356
  .define_method("shrink_to_fit", &T::shrink_to_fit)
357
357
  .define_method("[]=", [this](T& vector, Difference_T index, Parameter_T element) -> void
358
358
  {
359
359
  index = normalizeIndex(vector.size(), index, true);
360
360
  vector[index] = std::move(element);
361
- });
361
+ }, Arg("pos"), Arg("value").keepAlive());
362
362
 
363
363
  rb_define_alias(klass_, "push_back", "push");
364
364
  rb_define_alias(klass_, "<<", "push");
@@ -492,7 +492,19 @@ namespace Rice
492
492
  case RUBY_T_ARRAY:
493
493
  if constexpr (std::is_default_constructible_v<T>)
494
494
  {
495
- return Convertible::Exact;
495
+ // For proper overload resolution when a function has multiple vector type
496
+ // overloads (e.g., vector<A>& and vector<B>&), we must check if the array
497
+ // elements can actually be converted to T. Otherwise all vector overloads
498
+ // score equally and the wrong one may be selected.
499
+ long size = RARRAY_LEN(value);
500
+ if (size == 0)
501
+ {
502
+ return Convertible::Exact;
503
+ }
504
+
505
+ From_Ruby<remove_cv_recursive_t<T>> fromRuby;
506
+ VALUE first = rb_ary_entry(value, 0);
507
+ return fromRuby.is_convertible(first);
496
508
  }
497
509
  default:
498
510
  return Convertible::None;
@@ -548,7 +560,19 @@ namespace Rice
548
560
  case RUBY_T_ARRAY:
549
561
  if constexpr (std::is_default_constructible_v<T>)
550
562
  {
551
- return Convertible::Exact;
563
+ // For proper overload resolution when a function has multiple vector type
564
+ // overloads (e.g., vector<A>& and vector<B>&), we must check if the array
565
+ // elements can actually be converted to T. Otherwise all vector overloads
566
+ // score equally and the wrong one may be selected.
567
+ long size = RARRAY_LEN(value);
568
+ if (size == 0)
569
+ {
570
+ return Convertible::Exact;
571
+ }
572
+
573
+ From_Ruby<remove_cv_recursive_t<T>> fromRuby;
574
+ VALUE first = rb_ary_entry(value, 0);
575
+ return fromRuby.is_convertible(first);
552
576
  }
553
577
  default:
554
578
  return Convertible::None;
@@ -609,7 +633,19 @@ namespace Rice
609
633
  case RUBY_T_ARRAY:
610
634
  if constexpr (std::is_default_constructible_v<T>)
611
635
  {
612
- return Convertible::Exact;
636
+ // For proper overload resolution when a function has multiple vector type
637
+ // overloads (e.g., vector<A>& and vector<B>&), we must check if the array
638
+ // elements can actually be converted to T. Otherwise all vector overloads
639
+ // score equally and the wrong one may be selected.
640
+ long size = RARRAY_LEN(value);
641
+ if (size == 0)
642
+ {
643
+ return Convertible::Exact;
644
+ }
645
+
646
+ From_Ruby<remove_cv_recursive_t<T>> fromRuby;
647
+ VALUE first = rb_ary_entry(value, 0);
648
+ return fromRuby.is_convertible(first);
613
649
  }
614
650
  default:
615
651
  return Convertible::None;
@@ -22,6 +22,21 @@ TEARDOWN(Attribute)
22
22
 
23
23
  namespace
24
24
  {
25
+ class MyClass2
26
+ {
27
+ public:
28
+ MyClass2(int value) : value(value)
29
+ {
30
+ }
31
+
32
+ ~MyClass2()
33
+ {
34
+ value = -1;
35
+ }
36
+
37
+ int value = 0;
38
+ };
39
+
25
40
  class SomeClass
26
41
  {
27
42
  };
@@ -70,6 +85,7 @@ namespace
70
85
  char buf[2] = { '0', '1' };
71
86
  OldEnum oldEnum = OldValue1;
72
87
  NewEnum newEnum = NewEnum::NewValue1;
88
+ MyClass2* myClass2 = nullptr;
73
89
 
74
90
  std::string inspect()
75
91
  {
@@ -496,4 +512,60 @@ TESTCASE(CounterBufferAttribute)
496
512
  struct.counters_buffer.class)";
497
513
  Class klass = (Class)m.module_eval(code);
498
514
  ASSERT_EQUAL("Rice::Pointer≺AnonymousNamespace꞉꞉Counter≻", klass.name().c_str());
515
+ }
516
+
517
+ TESTCASE(TakeOwnership)
518
+ {
519
+ Module m = define_module("Testing");
520
+
521
+ define_class<MyClass2>("MyClass2")
522
+ .define_constructor(Constructor<MyClass2, int>());
523
+
524
+ define_class<DataStruct>("DataStruct")
525
+ .define_constructor(Constructor<DataStruct>())
526
+ .define_attr("my_class2", &DataStruct::myClass2, Rice::AttrAccess::ReadWrite, Arg("value").takeOwnership());
527
+
528
+ // Create DataStruct, create MyClass2, assign it, nil it, GC, then verify MyClass2 is still alive
529
+ std::string code = R"(
530
+ data_struct = DataStruct.new
531
+ my_class2 = MyClass2.new(42)
532
+ data_struct.my_class2 = my_class2
533
+ my_class2 = nil
534
+ GC.start
535
+ data_struct
536
+ )";
537
+
538
+ Data_Object<DataStruct> dataStruct = m.module_eval(code);
539
+
540
+ // This should work because ownership was transferred - MyClass2 is now owned by C++
541
+ ASSERT_NOT_EQUAL(nullptr, dataStruct->myClass2);
542
+ ASSERT_EQUAL(42, dataStruct->myClass2->value);
543
+ }
544
+
545
+ TESTCASE(KeepAlive)
546
+ {
547
+ Module m = define_module("Testing");
548
+
549
+ define_class<MyClass2>("MyClass2")
550
+ .define_constructor(Constructor<MyClass2, int>());
551
+
552
+ define_class<DataStruct>("DataStruct")
553
+ .define_constructor(Constructor<DataStruct>())
554
+ .define_attr("my_class2", &DataStruct::myClass2, Rice::AttrAccess::ReadWrite, Arg("value").keepAlive());
555
+
556
+ // Create DataStruct, create MyClass2, assign it, nil it, GC, then verify MyClass2 is still alive
557
+ std::string code = R"(
558
+ data_struct = DataStruct.new
559
+ my_class2 = MyClass2.new(43)
560
+ data_struct.my_class2 = my_class2
561
+ my_class2 = nil
562
+ GC.start
563
+ data_struct
564
+ )";
565
+
566
+ Data_Object<DataStruct> dataStruct = m.module_eval(code);
567
+
568
+ // This should work because keepAlive prevents MyClass2 from being GC'd
569
+ ASSERT_NOT_EQUAL(nullptr, dataStruct->myClass2);
570
+ ASSERT_EQUAL(43, dataStruct->myClass2->value);
499
571
  }
@@ -55,7 +55,10 @@ TESTCASE(LambdaCallBack)
55
55
  ASSERT((globalCallback != nullptr));
56
56
 
57
57
  int ref = 4;
58
+ #pragma GCC diagnostic push
59
+ #pragma GCC diagnostic ignored "-Wwrite-strings"
58
60
  char* result = triggerCallback(1, 2, true, "hello", ref);
61
+ #pragma GCC diagnostic pop
59
62
  ASSERT_EQUAL("1 - 2.0 - true - hello - 4", result);
60
63
  }
61
64
 
@@ -128,12 +128,12 @@ TESTCASE(base_pointer_method_call)
128
128
 
129
129
  Module m = define_module("Testing");
130
130
 
131
- Object message = m.module_eval(R"EOS(notification = EmailNotification.new
132
- notification.message)EOS");
131
+ Object message = m.module_eval(R"(notification = EmailNotification.new
132
+ notification.message)");
133
133
  ASSERT_EQUAL("Email", detail::From_Ruby<std::string>().convert(message));
134
134
 
135
- message = m.module_eval(R"EOS(notification = PushNotification.new
136
- notification.message)EOS");
135
+ message = m.module_eval(R"(notification = PushNotification.new
136
+ notification.message)");
137
137
  ASSERT_EQUAL("Push", detail::From_Ruby<std::string>().convert(message));
138
138
  }
139
139
 
@@ -151,12 +151,12 @@ TESTCASE(base_pointer_function_argument)
151
151
  define_global_function("process_notification", &processNotification);
152
152
 
153
153
  Module m = define_module("Testing");
154
- Object message = m.module_eval(R"EOS(notification = EmailNotification.new
155
- process_notification(notification))EOS");
154
+ Object message = m.module_eval(R"(notification = EmailNotification.new
155
+ process_notification(notification))");
156
156
  ASSERT_EQUAL("Email", detail::From_Ruby<std::string>().convert(message));
157
157
 
158
- message = m.module_eval(R"EOS(notification = PushNotification.new
159
- process_notification(notification))EOS");
158
+ message = m.module_eval(R"(notification = PushNotification.new
159
+ process_notification(notification))");
160
160
  ASSERT_EQUAL("Push", detail::From_Ruby<std::string>().convert(message));
161
161
  }
162
162
 
@@ -175,12 +175,12 @@ TESTCASE(module_base_pointer_method_call)
175
175
 
176
176
  Module m = define_module("Testing");
177
177
 
178
- Object message = m.module_eval(R"EOS(notification = Inheritance::EmailNotification.new
179
- notification.message)EOS");
178
+ Object message = m.module_eval(R"(notification = Inheritance::EmailNotification.new
179
+ notification.message)");
180
180
  ASSERT_EQUAL("Email", detail::From_Ruby<std::string>().convert(message));
181
181
 
182
- message = m.module_eval(R"EOS(notification = Inheritance::PushNotification.new
183
- notification.message)EOS");
182
+ message = m.module_eval(R"(notification = Inheritance::PushNotification.new
183
+ notification.message)");
184
184
  ASSERT_EQUAL("Push", detail::From_Ruby<std::string>().convert(message));
185
185
  }
186
186
 
@@ -219,8 +219,8 @@ TESTCASE(base_pointer_constructor)
219
219
 
220
220
  Module m = define_module("Testing");
221
221
 
222
- Object result = m.module_eval(R"EOS(notification = PushNotification.new
222
+ Object result = m.module_eval(R"(notification = PushNotification.new
223
223
  processor = Processor.new(notification)
224
- processor.process)EOS");
224
+ processor.process)");
225
225
  ASSERT_EQUAL("Push", detail::From_Ruby<std::string>().convert(result));
226
226
  }
@@ -75,11 +75,15 @@ TESTCASE(test_keep_alive_no_wrapper)
75
75
  Module m = define_module("TestingModule");
76
76
  Object zoo = m.module_eval("@zoo = Zoo.new");
77
77
 
78
+ std::string code = R"(@zoo.get_pets.each do |pet|
79
+ puts pet.get_name
80
+ end)";
81
+
78
82
  // get_pets returns an Array (builtin type) so Return().keepAlive()
79
- // shall result in std::runtime_error
83
+ // should result in std::runtime_error
80
84
  ASSERT_EXCEPTION_CHECK(
81
85
  Exception,
82
- m.module_eval("@zoo.get_pets.each do |pet| puts pet.name; end"),
86
+ m.module_eval(code),
83
87
  ASSERT_EQUAL("wrong argument type Array (expected wrapped C++ object)",
84
88
  ex.what())
85
89
  );
@@ -706,3 +706,49 @@ TESTCASE(MapToHash)
706
706
  ASSERT_EQUAL("2", detail::From_Ruby<std::string>().convert(hash["Two"].value()));
707
707
  ASSERT_EQUAL("3", detail::From_Ruby<std::string>().convert(hash["Three"].value()));
708
708
  }
709
+
710
+ namespace
711
+ {
712
+ class MyClass2
713
+ {
714
+ public:
715
+ MyClass2(std::string name): name(name)
716
+ {
717
+ }
718
+ std::string name;
719
+ };
720
+ }
721
+
722
+ TESTCASE(KeepAlive)
723
+ {
724
+ Class c = define_class<MyClass2>("MyClass2").
725
+ define_constructor(Constructor<MyClass2, std::string>()).
726
+ define_attr("name", &MyClass2::name, AttrAccess::Read);
727
+
728
+ define_map<std::string, MyClass2*>("MyClass2PointerMap");
729
+
730
+ Module m = define_module("Testing");
731
+
732
+ std::string code = R"(
733
+ map = Std::MyClass2PointerMap.new
734
+
735
+ # Test []=
736
+ map["one"] = MyClass2.new("instance1")
737
+ map["two"] = MyClass2.new("instance2")
738
+ map["three"] = MyClass2.new("instance3")
739
+
740
+ GC.start
741
+
742
+ names = []
743
+ map.each do |pair|
744
+ names << pair.second.name
745
+ end
746
+ names.sort
747
+ )";
748
+
749
+ Array result = m.module_eval(code);
750
+ ASSERT_EQUAL(3, result.size());
751
+ ASSERT_EQUAL("instance1", detail::From_Ruby<std::string>().convert(result[0].value()));
752
+ ASSERT_EQUAL("instance2", detail::From_Ruby<std::string>().convert(result[1].value()));
753
+ ASSERT_EQUAL("instance3", detail::From_Ruby<std::string>().convert(result[2].value()));
754
+ }
@@ -690,4 +690,50 @@ TESTCASE(HashToMultimapMixedTypes)
690
690
  m.module_eval(code),
691
691
  ASSERT_EQUAL("no implicit conversion of String into Integer", ex.what())
692
692
  );
693
+ }
694
+
695
+ namespace
696
+ {
697
+ class MyClass2
698
+ {
699
+ public:
700
+ MyClass2(std::string name): name(name)
701
+ {
702
+ }
703
+ std::string name;
704
+ };
705
+ }
706
+
707
+ TESTCASE(KeepAlive)
708
+ {
709
+ Class c = define_class<MyClass2>("MyClass2").
710
+ define_constructor(Constructor<MyClass2, std::string>()).
711
+ define_attr("name", &MyClass2::name, AttrAccess::Read);
712
+
713
+ define_multimap<std::string, MyClass2*>("MyClass2PointerMultimap");
714
+
715
+ Module m = define_module("Testing");
716
+
717
+ std::string code = R"(
718
+ map = Std::MyClass2PointerMultimap.new
719
+
720
+ # Test insert
721
+ map.insert("one", MyClass2.new("instance1"))
722
+ map.insert("two", MyClass2.new("instance2"))
723
+ map.insert("three", MyClass2.new("instance3"))
724
+
725
+ GC.start
726
+
727
+ names = []
728
+ map.each do |pair|
729
+ names << pair.second.name
730
+ end
731
+ names.sort
732
+ )";
733
+
734
+ Array result = m.module_eval(code);
735
+ ASSERT_EQUAL(3, result.size());
736
+ ASSERT_EQUAL("instance1", detail::From_Ruby<std::string>().convert(result[0].value()));
737
+ ASSERT_EQUAL("instance2", detail::From_Ruby<std::string>().convert(result[1].value()));
738
+ ASSERT_EQUAL("instance3", detail::From_Ruby<std::string>().convert(result[2].value()));
693
739
  }
@@ -788,3 +788,37 @@ TESTCASE(Superset)
788
788
  Object result = m.instance_eval(code);
789
789
  ASSERT_EQUAL(Qtrue, result.value());
790
790
  }
791
+
792
+ TESTCASE(KeepAlive)
793
+ {
794
+ Class c = define_class<MyClass2>("MyClass2").
795
+ define_constructor(Constructor<MyClass2, std::string>()).
796
+ define_attr("name", &MyClass2::name, AttrAccess::Read);
797
+
798
+ define_set<MyClass2*>("MyClass2PointerSet");
799
+
800
+ Module m = define_module("Testing");
801
+
802
+ std::string code = R"(
803
+ set = Std::MyClass2PointerSet.new
804
+
805
+ # Test insert
806
+ set.insert(MyClass2.new("instance1"))
807
+ set.insert(MyClass2.new("instance2"))
808
+ set.insert(MyClass2.new("instance3"))
809
+
810
+ GC.start
811
+
812
+ names = []
813
+ set.each do |instance|
814
+ names << instance.name
815
+ end
816
+ names.sort
817
+ )";
818
+
819
+ Array result = m.module_eval(code);
820
+ ASSERT_EQUAL(3, result.size());
821
+ ASSERT_EQUAL("instance1", detail::From_Ruby<std::string>().convert(result[0].value()));
822
+ ASSERT_EQUAL("instance2", detail::From_Ruby<std::string>().convert(result[1].value()));
823
+ ASSERT_EQUAL("instance3", detail::From_Ruby<std::string>().convert(result[2].value()));
824
+ }