yorel::yomm2::virtual_ptr
yorel::yomm2::virtual_shared_ptr
yorel::yomm2::make_virtual_shared
defined in <yorel/yomm2/core.hpp>, also provided by <yorel/yomm2/keywords.hpp>, <yorel/yomm2.hpp>
template<class Class, class Policy = YOMM2_DEFAULT_POLICY>
class virtual_ptr;
virtual_ptr
is a fat pointer that consists of a pointer to an object, and a
pointer to its associated method table. It can be used in declare_method
, in
place of the virtual_
, for virtual method parameters.
Unlike virtual_
, virtual_ptr
should be used in method definitions as well -
see examples below.
virtual_ptr
can be passed either by value, or by const reference. In general,
passing by value should be preferred, as it makes it possible to pass the two
embedded pointers via registers. However, see the paragraph about
specializations below.
Note: calling update
invalidates existing virtual_ptr
s.
Class
- The type of the object.
pOLICY
- The type of the object.
template<class Class, class Policy = default_policy>
class virtual_ptr<std::shared_ptr<Class>>;
This specialization uses the standard shared_ptr
to store the address of the
object.
Virtual shared pointers should be passed by const reference, to avoid excessive manipulations of the reference count.
(constructor) | constructs a new virtual_ptr |
get | returns the stored pointer |
operator* | dereferences the stored pointer |
operator-> | dereferences the stored pointer |
template<class Other> final(Other& obj) | returns a new virtual_ptr |
template<class Class> virtual_shared_ptr | alias for virtual_ptr<std::shared_ptr<Class>> |
template<class Class> make_virtual_shared() | creates an object and returns a new virtual_ptr |
template<class Other$gt; virtual_ptr(Other& obj) |
(1) |
template<class Other$gt; virtual_ptr(const virtual_ptr<Other>& other) |
(2) |
(1) Constructs a virtual_ptr
that contains a reference to obj
, which must be
compatible with Class
, and a pointer to the method table corresponding to
obj
’s dynamic type. If the dynamic type of obj
is the same as Other
(i.e. typeid(obj) == typeid(Other)
), the hash table is not looked up.
(2) Constructs a virtual_ptr
that contains a reference to obj
, and a pointer
to the method table corresponding to obj
’s dynamic type. If the dynamic type
of obj
is the same as Other
(i.e. typeid(obj) == typeid(Other)
),
the hash table is not looked up.
element_type* get() const noexcept; |
Returns the stored pointer.
(none)
The stored pointer.
Class& operator*() const noexcept; |
Class* operator->() const noexcept; |
Dereferences the stored pointer.
(none)
1) The result of dereferencing the stored pointer, i.e., *get(). 2) The stored pointer, i.e., get().
template<class Other$gt; static final(Other& obj) |
(1) |
Constructs a virtual_ptr
that contains a reference to obj
, which must be
compatible with Class
, and a pointer to the method table corresponding to
obj
’s static type. Neither Class
nor Other
are not required to be
polymorphic types.
In debug builds, final
compares typeid(Class)
and typeid(obj)
. If they are
different, error_handler
is called with a method_table_error
object. This
can help detect misuses of final
, but only for polymorphic classes.
virtual_shared_ptr<Class>
is an alias for
virtual_ptr<std::shared_ptr<Class>>
.
template<class Class$gt; make_virtual_shared() |
Constructs an object, using std::make_shared
, and return a virtual_ptr
to
it. No hash table lookup is performed.
This construct is always safe to use, even with non-polymorphic types.
Calls to methods through a virtual_ptr
are almost as efficient as virtual
function calls, because they do not require a hash table lookup, unlike calls
made using the orthogonal mode. The lookup is performed only once, when the
pointer is created via a call to the constructor, and the result is cached.
Calling update
invalidates all the existing virtual_ptr
s.
using yorel::yomm2::virtual_ptr;
class Animal {
public:
virtual ~Animal() {
}
};
class Dog : public Animal {
};
class Cat : public Animal {
};
register_classes(Animal, Dog, Cat);
declare_method(void, kick, (virtual_ptr<Animal>, std::ostream&));
define_method(void, kick, (virtual_ptr<Dog> dog, std::ostream& os)) {
os << "bark";
}
define_method(void, kick, (virtual_ptr<Cat> cat, std::ostream& os)) {
os << "hiss";
}
declare_method(
void, meet, (virtual_ptr<Animal>, virtual_ptr<Animal>, std::ostream&));
define_method(
void, meet, (virtual_ptr<Dog> a, virtual_ptr<Dog> b, std::ostream& os)) {
os << "wag tail";
}
define_method(
void, meet, (virtual_ptr<Cat> a, virtual_ptr<Dog> b, std::ostream& os)) {
os << "run";
}
define_method(
void, meet, (virtual_ptr<Dog> a, virtual_ptr<Cat> b, std::ostream& os)) {
os << "chase";
}
BOOST_AUTO_TEST_CASE(ref_virtual_ptr) {
yorel::yomm2::update();
Dog snoopy, hector;
Cat sylvester;
std::vector<virtual_ptr<Animal>> animals;
animals.emplace_back(virtual_ptr<Animal>(snoopy));
animals.emplace_back(virtual_ptr<Animal>(sylvester));
animals.emplace_back(virtual_ptr<Animal>(hector));
{
boost::test_tools::output_test_stream os;
kick(animals[0], os);
BOOST_CHECK(os.is_equal("bark"));
}
{
boost::test_tools::output_test_stream os;
kick(animals[1], os);
BOOST_CHECK(os.is_equal("hiss"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[1], os);
BOOST_CHECK(os.is_equal("chase"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[2], os);
BOOST_CHECK(os.is_equal("wag tail"));
}
}
A call to kick
compiles to three instructions and two memory reads:
mov rax, qword ptr [rip + method<kick, ...>::fn+96]
mov rax, qword ptr [rsi + 8*rax]
jmp rax
A call to meet
compiles to:
mov rax, qword ptr [rip + method<meet, ...>::fn+96]
mov r8, qword ptr [rsi + 8*rax]
mov rax, qword ptr [rip + method<meet, ...>::fn+104]
mov rax, qword ptr [rcx + 8*rax]
imul rax, qword ptr [rip + method<meet, ...>::fn+112]
mov rax, qword ptr [r8 + 8*rax]
jmp rax
final
can be used to add polymorphic behavior to non-polymorphic classes. It
is the responsibility of the caller to ensure that the static type of the object
is, indeed, its exact type.
class Animal {
// note: no virtual functions
};
class Dog : public Animal {
};
class Cat : public Animal {
};
register_classes(Animal, Dog, Cat);
using yorel::yomm2::virtual_ptr;
declare_method(void, kick, (virtual_ptr<Animal>, std::ostream&));
define_method(void, kick, (virtual_ptr<Dog> dog, std::ostream& os)) {
os << "bark";
}
define_method(void, kick, (virtual_ptr<Cat> cat, std::ostream& os)) {
os << "hiss";
}
declare_method(
void, meet, (virtual_ptr<Animal>, virtual_ptr<Animal>, std::ostream&));
define_method(
void, meet, (virtual_ptr<Dog> a, virtual_ptr<Dog> b, std::ostream& os)) {
os << "wag tail";
}
define_method(
void, meet, (virtual_ptr<Cat> a, virtual_ptr<Dog> b, std::ostream& os)) {
os << "run";
}
define_method(
void, meet, (virtual_ptr<Dog> a, virtual_ptr<Cat> b, std::ostream& os)) {
os << "chase";
}
BOOST_AUTO_TEST_CASE(ref_virtual_ptr_final) {
yorel::yomm2::update();
Dog snoopy, hector;
Cat sylvester;
std::vector<virtual_ptr<Animal>> animals = {
virtual_ptr<Animal>::final(snoopy),
virtual_ptr<Animal>::final(sylvester),
virtual_ptr<Animal>::final(hector),
};
{
boost::test_tools::output_test_stream os;
kick(animals[0], os);
BOOST_CHECK(os.is_equal("bark"));
}
{
boost::test_tools::output_test_stream os;
kick(animals[1], os);
BOOST_CHECK(os.is_equal("hiss"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[1], os);
BOOST_CHECK(os.is_equal("chase"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[2], os);
BOOST_CHECK(os.is_equal("wag tail"));
}
}
For non-polymorphic types, YOMM2 cannot detect misuses of final
. At best, it
will result in a missing definition error, at worst the wrong definition will be
selected, as in the following example.
define_method(void, kick, (virtual_ptr<Animal> dog, std::ostream& os)) {
os << "wrong call";
}
BOOST_AUTO_TEST_CASE(ref_virtual_ptr_final_incorrect) {
yorel::yomm2::update();
Dog snoopy;
Animal& animal = snoopy;
auto animal_vptr = virtual_ptr<Animal>::final(animal);
boost::test_tools::output_test_stream os;
kick(animal_vptr, os);
BOOST_CHECK(os.is_equal("wrong call"));
}
The virtual_ptr<std::shared_ptr<Class>>
specialisation combines the fast
method dispatch of virtual_ptr
with the lifetime management of
std::shared_ptr
. Instead of using an ordinary pointer to store the reference
to the object, it uses a std::shared_ptr
.
virtual_shared_ptr<Class>
is an alias for
virtual_ptr<std::shared_ptr<Class>>
.
make_virtual_shared<Class>
calls std::make_shared<Class>
, and returns a
virtual_shared_ptr<Class>
. Since the exact type of the object is known, there
is no need to consult the hash table. For this reason, virtual_shared_ptr
is
safe to use for non-polymorphic types.
class Animal {
// it does not matter if the class has virtual functions
};
class Dog : public Animal {
};
class Cat : public Animal {
};
register_classes(Animal, Dog, Cat);
using yorel::yomm2::virtual_shared_ptr;
declare_method(void, kick, (const virtual_shared_ptr<Animal>&, std::ostream&));
define_method(void, kick, (
const virtual_shared_ptr<Dog>& dog, std::ostream& os)) {
os << "bark";
}
define_method(void, kick, (
const virtual_shared_ptr<Cat>& cat, std::ostream& os)) {
os << "hiss";
}
declare_method(
void, meet, (
const virtual_shared_ptr<Animal>&,
const virtual_shared_ptr<Animal>&, std::ostream&));
define_method(
void, meet, (
const virtual_shared_ptr<Dog>& a,
const virtual_shared_ptr<Dog>& b, std::ostream& os)) {
os << "wag tail";
}
define_method(
void, meet, (
const virtual_shared_ptr<Cat>& a,
const virtual_shared_ptr<Dog>& b, std::ostream& os)) {
os << "run";
}
define_method(
void, meet, (
const virtual_shared_ptr<Dog>& a,
const virtual_shared_ptr<Cat>& b, std::ostream& os)) {
os << "chase";
}
BOOST_AUTO_TEST_CASE(ref_make_virtual_shared) {
yorel::yomm2::update();
using yorel::yomm2::make_virtual_shared;
std::vector<virtual_shared_ptr<Animal>> animals = {
make_virtual_shared<Dog>(),
make_virtual_shared<Cat>(),
make_virtual_shared<Dog>(),
};
{
boost::test_tools::output_test_stream os;
kick(animals[0], os);
BOOST_CHECK(os.is_equal("bark"));
}
{
boost::test_tools::output_test_stream os;
kick(animals[1], os);
BOOST_CHECK(os.is_equal("hiss"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[1], os);
BOOST_CHECK(os.is_equal("chase"));
}
{
boost::test_tools::output_test_stream os;
meet(animals[0], animals[2], os);
BOOST_CHECK(os.is_equal("wag tail"));
}
}