Untitled :: Boost.OpenMethod

If we want maximum performance, we want to use `virtual_ptr`s 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_ptr`s 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_ptr`s, although it is not always the case [1].

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 for `virtual_ptr<std::unique_ptr<Class>>`

- `shared_virtual_ptr<Class>` is an alias for `virtual_ptr<std::shared_ptr<Class>>`

Support for `boost::intrusive_ptr` is also available:

- `boost_intrusive_virtual_ptr<Class>` is an alias for `virtual_ptr<boost::intrusive_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`

- `make_shared_virtual`

- `make_boost_intrusive_virtual`

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_ptr`s 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>`, and support for `boost::intrusive_ptr` is provided in `<boost/openmethod/interop/boost_intrusive_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
    }

1. 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.
Edit this Page