NAME
XS::Framework::Manual::recipe06 - XS::Framework advanced topic
DESCRIPTION
If you have control on C++ API you'd like to port on Perl, or the API was designed for integration with scripting languages, then C++ objects give dedicated or, more commonly, shared
ownership, i.e. they might be held from a script or from other C++ objects together.
The most common approach to achive that is to use refcounted policy on a C++ class. The most common approach in C++ world is to use std::shared_ptr
, however, if you have access to sources, we do not recommend using it as it provides non-zero costs on its thread-safety guarantees. In Perl it is rather exceptional use threads and do inter-thread communications, that's why we'd recommend to avoid using std::shared_ptr
.
Another approach is to use local_shared_ptr or intrusive_ptr from boost library. The former is similar to std::shared_ptr
without the thread-safety guarantees, i.e. optimal for using in Perl. The later assumes that refcounted policy is injected into C++ class itself, so it is able to do basic refcount-related operations: dec
, inc
and get use count. To simplify additions of the operations in C++ class the intrusive_ref_counter
is shipped: your class just have to inherit from it, and the counter and the operations will appear in the C++ class.
The XS::libpanda already ships the Refcnt
/ iptr
with the similar to boost's intrusive pointer policy. Let's show how to use it
struct TimezoneRecipe11: public panda::Refcnt {
// (1)
const char* get_name() const { return name.c_str(); }
private:
TimezoneRecipe11(const char* name_): name{name_} { }
std::string name;
friend class DateRecipe11;
};
using TimezoneRecipe11SP = panda::iptr<TimezoneRecipe11>;
struct DateRecipe11: public panda::Refcnt {
// (2)
DateRecipe11(const char* tz_ = "Europe/Minsk"): tz(new TimezoneRecipe11(tz_)) { update(); }
void update() { epoch = std::time(nullptr); }
int get_epoch() const { return epoch; }
TimezoneRecipe11SP get_timezone() { return tz; }
// (3)
private:
std::time_t epoch;
TimezoneRecipe11SP tz;
// (4)
};
All is required to support refcount-policy on a C++ class, is just inherit from panda::Refcnt
in your classes (1) and (2). Then, obviously, C++ class have to hold smpart pointer (iptr
) of the dependendent class, instead of object or object pointer or object reference. If a object embeds another object, that means exclusive ownership; if the object embeds a reference to another object, that means <no ownership> on it; and the pointer case means either exlcusive or no ownership. iptr
(or std::shared_ptr
) grants shared ownership (4). The operation get_timezone
(3) above actually does timezone sharing.
namespace xs {
template <>
struct Typemap<DateRecipe11*> : TypemapObject<DateRecipe11*, DateRecipe11*, ObjectTypeRefcntPtr, ObjectStorageMG> {
// (5)
static std::string package () { return "MyTest::Cookbook::DateRecipe11"; }
};
template <>
struct Typemap<TimezoneRecipe11*> : TypemapObject<TimezoneRecipe11*, TimezoneRecipe11*, ObjectTypeRefcntPtr, ObjectStorageMG> {
// (6)
static std::string package () { return "MyTest::Cookbook::TimezoneRecipe11"; }
};
};
The typemaps for the Date
and Timezone
are rather trivial; the only moment to note is the ObjectTypeRefcntPtr
lifetime policy in (5) and (6).
MODULE = MyTest PACKAGE = MyTest::Cookbook::TimezoneRecipe11
PROTOTYPES: DISABLE
const char* TimezoneRecipe11::get_name()
MODULE = MyTest PACKAGE = MyTest::Cookbook::DateRecipe11
PROTOTYPES: DISABLE
DateRecipe11* DateRecipe11::new(const char* name)
void DateRecipe11::update()
std::time_t DateRecipe11::get_epoch()
TimezoneRecipe11SP DateRecipe11::get_timezone()
The xs-adapters are also pretty trivial.
Please note, that XS::Framework is shipsed with typemap auto-deduction rules. Defining typemap for type T
, the typemap for iptr<T> is auto-decuded.
That's not true for std::shared_ptr
(and for boost::local_shared_ptr
) as the refcounter is stored outside of an object. So, if in xs-adapter the std::shared_ptr<T> is used, the typemap for std::shared_ptr<T> should be defined; if, in addition, the T* pointer is used, then, in addition the typemap<T*> for it should be defined also. Do not be confused with the shipped ObjectTypeSharedPtr
lifetime policy for std::shared_ptr
for TypemapObject
: the typemap for std::shared_ptr<T> still have to be defined, probaby with help of TypemapObject
.
It is possible, that C++ class has it's own refcounted inferface. Then, to use ObjectTypeRefcntPtr
lifetime policy, the following free functions must be defined for it:
void refcnt_inc(MyClass*);
void refcnt_dec(MyClass*);
std::uint32_t refcnt_get(MyClass*);
Short summary: if C++ API offers shared ownership on objects, then it is friendly for integration into scripting languages. XS::libpanda ships Refcnt
and iptr
helpers following intrusive refcounter approach. ObjectTypeRefcntPtr
lifetime policy for TypemapObject
helps to adopt refcounted objects into Perl.