Reference semantics#
In the first tutorial, we mentioned how one of the reasons to prefer type-erasure over traditional object-oriented programming is that with type-erasure we can implement value semantics, rather than being forced into pointer/reference semantics.
There are however situations in which pointer/reference semantics might be preferrable. For instance, the motivating example for the development of reference semantics in tanuki was the necessity to support large computational graphs featuring a high degree of internal repetition in the heyoka library.
Reference semantics support in tanuki is activated by setting the config::semantics
config option to wrap_semantics::reference
. When reference semantics
is activated, the wrap
class will employ internally a
shared pointer to store the type-erased
value, and copy/move/swap operations on a wrap
will behave like the corresponding
operations on a shared pointer - that is, they will manipulate references to the internal value,
rather than the value itself.
Additionally, there are a couple of extra functions in the API that are available only when using reference semantics.
The first one, wrap::copy()
, is used to make a deep-copy of a wrap
(that is, it enforces the creation of a copy of the internal value, rather than creating
a new shared reference to it).
The second one, wrap::same_value()
, can be used to detect if two wrap
objects share ownership of the same value.
Let us see a simple example of reference semantics in action. We begin with our usual, super-simple interface and its implementation:
template <typename Base, typename Holder, typename T>
struct any_iface_impl : public Base {
};
struct any_iface {
template <typename Base, typename Holder, typename T>
using impl = any_iface_impl<Base, Holder, T>;
};
We introduce a wrap
type employing reference semantics:
using any_wrap = tanuki::wrap<any_iface, tanuki::config<>{.semantics = tanuki::wrap_semantics::reference}>;
And we create a wrap
instance storing a std::string
:
any_wrap w1(std::string("hello world"));
Let us now make a copy of w1
:
auto w2 = w1;
As explained earlier, this operation will not copy the string inside w1
, rather it will create a new reference to it.
We can confirm that this indeed is the case by comparing the addresses of the values stored in w1
and w2
:
std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
std::cout << "Address of the value wrapped by w2: " << tanuki::value_ptr<std::string>(w2) << '\n';
Address of the value wrapped by w1: 0x606000000038
Address of the value wrapped by w2: 0x606000000038
We can also invoke the wrap::same_value()
function for further confirmation:
std::cout << "Do w1 and w2 share ownership? " << same_value(w1, w2) << '\n';
Do w1 and w2 share ownership? true
If, on the other hand, we use the wrap::copy()
function, we will be performing a copy
of the string stored in w1
:
auto w3 = copy(w1);
std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
std::cout << "Address of the value wrapped by w3: " << tanuki::value_ptr<std::string>(w3) << '\n';
std::cout << "Do w1 and w3 share ownership? " << same_value(w1, w3) << '\n';
Address of the value wrapped by w1: 0x606000000038
Address of the value wrapped by w3: 0x606000000098
Do w1 and w3 share ownership? false
Full code listing#
#include <ios>
#include <iostream>
#include <string>
#include <tanuki/tanuki.hpp>
template <typename Base, typename Holder, typename T>
struct any_iface_impl : public Base {
};
struct any_iface {
template <typename Base, typename Holder, typename T>
using impl = any_iface_impl<Base, Holder, T>;
};
int main()
{
using any_wrap = tanuki::wrap<any_iface, tanuki::config<>{.semantics = tanuki::wrap_semantics::reference}>;
any_wrap w1(std::string("hello world"));
auto w2 = w1;
std::cout << std::boolalpha;
std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
std::cout << "Address of the value wrapped by w2: " << tanuki::value_ptr<std::string>(w2) << '\n';
std::cout << "Do w1 and w2 share ownership? " << same_value(w1, w2) << '\n';
auto w3 = copy(w1);
std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
std::cout << "Address of the value wrapped by w3: " << tanuki::value_ptr<std::string>(w3) << '\n';
std::cout << "Do w1 and w3 share ownership? " << same_value(w1, w3) << '\n';
}