Alternatives to virtual_ptr
Virtual arguments can be passed as plain references. In a method declaration,
parameters with a type decorated with virtual_
are considered in overrider
selection (along with virtual_ptr
parameters).
For example, the poke
open-method in the Animals example can be rewritten as:
struct Animal {
virtual ~Animal() = default;
};
struct Cat : Animal {};
using boost::openmethod::virtual_;
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& /*cat*/), void) {
os << "hiss";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat);
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
Note that virtual_
is not used in the overrider. It is also removed from the
method’s signature.
By itself, virtual_
does not provide any benefits. Passing the virtual
argument by reference almost compiles to the same code as creating a
virtual_ptr
, using it for one call, then throwing it way. The only difference
is that the virtual argument is passed as one pointer instead of two.
However, we can now customize how the vptr is obtained. When the method sees a
virtual_
parameter, it looks for a boost_openmethod_vptr
function that takes
the parameter (by const reference), and returns a vptr_type
. If one is found,
it is called to obtain the vptr. The vptr for a specific registered class can be
obtained via a variable template static_vptr
, nested in class default_registry
(more on policies below).
In the following example, we embed a vptr in the object, just like the vptr for native virtual functions:
class Animal {
protected:
boost::openmethod::vptr_type vptr;
friend auto boost_openmethod_vptr(const Animal& a, void*) {
return a.vptr;
}
public:
Animal() {
vptr = boost::openmethod::default_registry::static_vptr<Animal>;
}
};
class Cat : public Animal {
public:
Cat() {
vptr = boost::openmethod::default_registry::static_vptr<Cat>;
}
};
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& /*cat*/), void) {
os << "hiss\n";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat);
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
With this approach, classes need not be polymorphic. A virtual destructor might be needed for correct destruction of objects, but it is not required by the library. |
The inplace_vptr
CRTP class automates the creation and management of embedded
vptrs.
#include <boost/openmethod/inplace_vptr.hpp>
class Animal : public boost::openmethod::inplace_vptr<Animal> {
};
class Cat : public Animal, public boost::openmethod::inplace_vptr<Cat, Animal> {
};
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& /*cat*/), void) {
os << "hiss\n";
}
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
If inplace_vptr
is passed only the class being defined, it adds a vptr to it, and
defines a boost_openmethod_vptr
friend function. If more classes are passed,
they must be the direct bases of the class potentially involved in open-method
calls. Its constructor and destructor set the vptr to point to the v-table for
the class. inplace_vptr
also takes care of registering the classes, so this time
the call to BOOST_OPENMETHOD_CLASSES
is not needed.