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:
class MyClass
{
public:
int flag = 0;
public:
void setFlag(int value)
{
this->flag = value;
}
};
class Factory
{
public:
std::unique_ptr<MyClass> transfer()
{
return std::make_unique<MyClass>();
}
};
int extractFlagUniquePtrRef(std::unique_ptr<MyClass>& myClass)
{
return myClass->flag;
}
void setupRice()
{
define_class<MyClass>("MyClass").
define_method("set_flag", &MyClass::setFlag);
define_class<Factory>("Factory").
define_constructor(Constructor<Factory>()).
define_method("transfer", &Factory::transfer);
define_global_function("extract_flag_unique_ptr_ref", &extractFlagUniquePtrRef);
}
And in 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.
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:
namespace Rice::detail
{
template <template <typename, typename...> typename SmartPointer_T, typename...Arg_Ts>
class WrapperSmartPointer : public Wrapper
{
public:
WrapperSmartPointer(SmartPointer_T<Arg_Ts...>& data);
void* get() override;
SmartPointer_T<Arg_Ts...>& data();
private:
SmartPointer_T<Arg_Ts...> data_;
};
}
Assuming that your smart pointer fits into the template class, you need to tell Rice three things:
How to wrap it in a Ruby object
How to extract it from a Ruby object
How to access its managed type
Let’s start by telling Rice how to wrap the smart pointer. Here is how that is done for std::unique_ptr
:
namespace Rice::detail
{
template <typename T>
struct To_Ruby<std::unique_ptr<T>>
{
static VALUE convert(std::unique_ptr<T>& data, bool takeOwnership = true)
{
std::pair<VALUE, rb_data_type_t*> rubyTypeInfo = detail::Registries::instance.types.figureType<T>(*data);
// Use custom wrapper type
using Wrapper_T = WrapperSmartPointer<std::unique_ptr, T>;
return detail::wrap<std::unique_ptr<T>, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true);
}
};
}
First we specialize detail::To_Ruby
for our smart pointer type, in this case std::unique_ptr
.
Next we pass the pointer to Rice’s TypeRegistry to look up what Ruby class wraps the C++ class. Then we instantiate a Wrapper_T class from the WrapperSmartPointer
. Finally we pass the type information and pointer to a helper method that will first create an instance of Wrapper_T and then an a Ruby object to store it.
The second step is to tell Rice how to extract the smart pointer from Ruby.
namespace Rice::detail
{
template <typename T>
struct From_Ruby<std::unique_ptr<T>&>
{
static std::unique_ptr<T>& convert(VALUE value)
{
Wrapper* wrapper = detail::getWrapper(value, Data_Type<T>::ruby_data_type());
using Wrapper_T = WrapperSmartPointer<std::unique_ptr, T>;
Wrapper_T* smartWrapper = dynamic_cast<Wrapper_T*>(wrapper);
if (!smartWrapper)
{
std::string message = "Invalid smart pointer wrapper";
throw std::runtime_error(message.c_str());
}
return smartWrapper->data();
}
};
}
Similarly to above, we specialize detail::From_Ruby
for our smart pointer type, in this case std::unique_ptr
.
Next we use a helper method to get back a pointer to the Wrapper
instance we created in the detail::To_Ruby
method. Note detail::Wrapper
is the base class for our custom wrapper type, so we have to typecast it to Wrapper_T
. Finally we call its data()
method to access to stored smart pointer.
Last, we have to tell Rice how to verify the type our smart pointer contains. That is done like this:
namespace Rice::detail
{
template<typename T>
struct Type<std::unique_ptr<T>>
{
static bool verify()
{
return Type<T>::verify();
}
};
}