NAME

XS::Framework::Manual::recipe04 - XS::Framework basics

DESCRIPTION

Let's assume that we have Timezone object, which is available via std::shared_ptr.

struct TimezoneRecipe04 {
    const char* get_name() const { return name.c_str(); }
    static std::shared_ptr<TimezoneRecipe04> create(const char* name) {
        return std::make_shared<TimezoneRecipe04>(name);
    }
    TimezoneRecipe04(const char* name_): name{name_} { }
    ~TimezoneRecipe04() { std::cerr << "~TimezoneRecipe04()\n"; }
private:
    std::string name;
};

using TimezoneRecipe04SP = std::shared_ptr<TimezoneRecipe04>;
// (1)

It is recommended to have an alias(1) for smart_pointes, as there will be less code below. Let's define typemap for the shared pointer of of Timezone:

namespace xs {
    template <>
    struct Typemap<TimezoneRecipe04SP> : TypemapObject<TimezoneRecipe04SP, TimezoneRecipe04SP, ObjectTypeSharedPtr, ObjectStorageMG, StaticCast> {
        //              (1)                                     (2)             (3)                 (4)
        static std::string package () { return "MyTest::Cookbook::TimezoneRecipe04"; }
    };
}

As usual, we have to do full specialization (1) for the smart pointer; we are using TypemapObject helper and as there is no types hierarchy base class (2) and final class (3) point to the same type (timezone smart pointer). The shipped ObjectTypeSharedPtr life time policy should be used, as it is aware of std::shared_ptr and knows how to increase and decrease it's reference counter.

The xs-adapter will be like:

MODULE = MyTest                PACKAGE = MyTest::Cookbook::TimezoneRecipe04
PROTOTYPES: DISABLE

const char* get_name(TimezoneRecipe04SP tz) { RETVAL = tz->get_name(); }
//              (5)

TimezoneRecipe04SP create(const char* name) { RETVAL = TimezoneRecipe04::create(name); }

Pay attention to the XS-function definition (5). It would be error to write it as const char* TimezoneRecipe04::get_name, because XS::Framework::ParseXS will try to map THIS variable to TimezoneRecipe04*, i.e. timezone pointer; but there is no typemap exist for TimezoneRecipe04*, there is only typemap for TimezoneRecipe04SP, and C++ compiler will emit error.

For the sake of completeness there is another mapped C++ class, which uses TimezoneRecipe04SP. There is nothing new for a reader familiar with the previous recipes.

// C++ class
struct DateRecipe04 {
    DateRecipe04()  { update() ; }
    void update()   { epoch = std::time(nullptr); }

    int get_epoch() const { return epoch; }
    void set_timezone(TimezoneRecipe04SP tz_) { tz = tz_; }
    TimezoneRecipe04SP get_timezone() { return tz; }
private:
    std::time_t epoch;
    TimezoneRecipe04SP tz;
};

// typemap
namespace xs {
    template <>
    struct Typemap<DateRecipe04*> : TypemapObject<DateRecipe04*, DateRecipe04*, ObjectTypePtr, ObjectStorageMG, StaticCast> {
        static std::string package () { return "MyTest::Cookbook::DateRecipe04"; }
    };
}

// XS-adapter
MODULE = MyTest                PACKAGE = MyTest::Cookbook::DateRecipe04
PROTOTYPES: DISABLE

DateRecipe04* DateRecipe04::new() { RETVAL = new DateRecipe04(); }

void DateRecipe04::update()

std::time_t DateRecipe04::get_epoch()

TimezoneRecipe04SP DateRecipe04::get_timezone()

void DateRecipe04::set_timezone(TimezoneRecipe04SP tz)

If there will be test code like:

my $tz = MyTest::Cookbook::TimezoneRecipe04::create('Europe/Minsk');
my $date = MyTest::Cookbook::DateRecipe04->new;
$date->set_timezone($tz);
is $date->get_timezone->get_name, 'Europe/Minsk';   # (6)
is $date->get_timezone, $tz;                        # (7)

the check code on line (6) will pass, and on the line (7) will fail. This is because every time new wrapper (SV*) is created by typemap::out method. To handle that you can either a) overload eq method to let it extracts actual pointers from shared_ptr and does pointer comparison, or use typemaps with backrefs support, which are covered in future recipes.