Smart Pointers
If we want maximum performance, we want to use virtual_ptrs in place of
ordinary pointers or references. However, we may also want to use smart pointers
for lifetime management; think of std::unique_ptr, std::shared_ptr, or
boost::intrusive_ptr.
Does it mean that we have to choose between the performance of virtual_ptr and
the convenience of std::unique_ptr? Or carry around both types of smart
pointers? Fortunately, no. virtual_ptr can inter-operate with smart pointers.
If virtual_ptr recognizes that its template argument is a smart pointer class,
it uses that smart pointer to track the underlying object, instead of a plain
pointer. virtual_ptr<const Node> and virtual_ptr<std::unique_ptr<const
Node>> both point to a const Node; the former uses a plain const Node*
while the latter uses a std::unique_ptr<const Node>. Both carry the same
v-table pointer.
Smart virtual_ptrs automatically convert to their non-smart, "plain"
counterparts - e.g. from virtual_ptr<std::unique_ptr<const Node>> to
virtual_ptr<const Node>. Methods and overriders typically use plain
virtual_ptrs, although it is not always the case. For example,
consider a transpose method for matrices. If the matrix is symmetric, the
overrider should return its argument. This can be implemented by passing a
virtual_ptr<std::shared_ptr<const Matrix>> to the method.
The reverse conversion, from plain to smart, does not exist, because it would
have the potential to accidentally create smart pointers. Likewise, a smart
virtual_ptr can be constructed from a smart pointer, but not directly from a
plain reference or pointer.
The library provides aliases for standard smart pointers:
-
unique_virtual_ptr<Class>is an alias forvirtual_ptr<std::unique_ptr<Class>> -
shared_virtual_ptr<Class>is an alias forvirtual_ptr<std::shared_ptr<Class>>
The standard library provides std::make_unique and std::make_shared to
create smart pointers. They are convenient, robust in presence of exceptions,
and, in the case of std::shared_ptr, more efficient. OpenMethod provides
these counterparts:
-
make_unique_virtual_ptr<Class>(…) -
make_shared_virtual_ptr<Class>(…)
Since these functions create the object, they know its exact type with
certainty. Thus they don’t need to perform a hash table lookup to find the
appropriate v-table; they simply read it from a static variable. As a
consequence, they don’t require Class to be polymorphic.
The aliases and the make_* functions are aliased in namespace
boost::openmethod::aliases, making it convenient to import constructs that are
likely to be used together.
Smart virtual_ptrs are implemented in their own headers, found in the
interop subdirectory. For example, support for std::unique_ptr is provided
in <boost/openmethod/interop/std_unique_ptr.hpp>. <boost/openmethod.hpp>
does not include smart pointer headers, so they must be included explicitly.
Here is a variation of the AST example that uses dynamic allocation and unique pointers:
#include <boost/openmethod.hpp>
#include <boost/openmethod/interop/std_unique_ptr.hpp>
using namespace boost::openmethod::aliases;
struct Node {
virtual ~Node() {}
virtual int value() const = 0;
};
struct Variable : Node {
Variable(int value) : v(value) {}
int value() const override { return v; }
int v;
};
struct Plus : Node {
Plus(unique_virtual_ptr<const Node> left, unique_virtual_ptr<const Node> right)
: left(std::move(left)), right(std::move(right)) {}
int value() const override { return left->value() + right->value(); }
unique_virtual_ptr<const Node> left, right;
};
struct Times : Node {
Times(unique_virtual_ptr<const Node> left, unique_virtual_ptr<const Node> right)
: left(std::move(left)), right(std::move(right)) {}
int value() const override { return left->value() * right->value(); }
unique_virtual_ptr<const Node> left, right;
};
#include <iostream>
BOOST_OPENMETHOD(postfix, (virtual_ptr<const Node> node, std::ostream& os), void);
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<const Variable> var, std::ostream& os), void) {
os << var->v;
}
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<const Plus> plus, std::ostream& os), void) {
postfix(plus->left, os);
os << ' ';
postfix(plus->right, os);
os << " +";
}
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<const Times> times, std::ostream& os), void) {
postfix(times->left, os);
os << ' ';
postfix(times->right, os);
os << " *";
}
BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);
#include <boost/openmethod/initialize.hpp>
int main() {
boost::openmethod::initialize();
auto a = std::make_unique<Variable>(2);
auto b = std::make_unique<Variable>(3);
auto c = std::make_unique<Variable>(4);
auto d = make_unique_virtual<Plus>(std::move(a), std::move(b));
auto e = make_unique_virtual<Times>(std::move(d), std::move(c));
postfix(e, std::cout);
std::cout << " = " << e->value() << "\n"; // 2 3 + 4 * = 20
}