PrevUpHomeNext

Tutorial

Basic

Root Pointer was designed to be easy to use and to be versatile in terms of object variants it can refer to. Its only requirement is limited to the usage of a special type needed to instantiate objects referred to.

For example:

root_ptr<int> p = make_root<int>(11);

will instantiate a special object having an integer as one of its member. The pointer to the object is then passed to the root_ptr<int> that will manage its existence and later destroy and deallocate it when it is found to be no longer referenced.

The root_ptr<int> guarantees all associated allocations, cyclic or not, will be freed upon its destruction. Once the root is defined, we can derive a node_ptr<int> from it:

node_ptr<int> q = make_node<int>(p, 12);

A node_ptr<int> is an internal pointer in a set of objects which uses the information of the associated root_ptr<int> to define its length of existence. As its name suggests, this can be used, for example, as node pointers inside a container for a given root.

[Note] Note

A root_ptr derives from node_proxy and node_ptr.

[Note] Note

The first parameter of a make_node is always the associated node_proxy, given by root_ptr.

[Note] Note

make_root is used to generate a root_ptr and consequently make_node is used to generate a node_ptr.

For example:

struct list_node
{
    list_node(node_proxy const & x) : prior(x), next(x)
    {
    }

    ~list_node()
    {
    }

    node_ptr<list_node> prior;
    node_ptr<list_node> next;
};

struct list
{
    void clear()
    {
        root.reset();
    }

    void insert()
    {
        if (root.get() == 0)
        {
            root = make_root<list_node>(root); // 'root' is used 1 time here
        }
        else
        {
            root->next = make_node<list_node>(root, root); // 'root' is used 2 times here
            root->next->prior = root;
            root = root->next;
        }
    }

    root_ptr<list_node> root;
};

Notice that list::root is actually a root_ptr<list_node> and therefore a make_root<list_node> must be used to assign a value to it. Likewise list_node::next is in turn a node_ptr<list_node> and thus a make_node<list_node> must but used this time with the same root parameter twice because:

That's it! You now have a cyclic container that will implicitly get destructed with no need to iterate through all list_nodes.

See root_ptr_example1.cpp for different cases of its usage.

R-Value

A function can be written to use as an R-value:

root_ptr<int> foo()
{
    return make_root<int>(9);
}

and called

std::cout << * foo() << std::endl;

to output

R-value:
9
Slicing

Slicing is shown in

struct B
{
    int i;

    B() : i(9) {}
};

struct C : B
{
};

void bar(node_ptr<B> p)
{
    std::cout << p->i << std::endl;
}

and called

root_ptr<C> p = make_root<C>();
bar(p);

with output

Slicing:
9
Sharing

Sharing is shown in

root_ptr<int> p = make_root<int>(9);
root_ptr<int> q = p;

std::cout << "p: " << *p << std::endl;
std::cout << "q: " << *q << std::endl;

with output

Sharing:
p: 9
q: 9

Advanced

Intermix

Although it is not recommended to do so, it is possible to intermix root_ptr and node_ptr with their associated make_root and make_node:

root_ptr<int> rp1, rp2, rp3;
node_ptr<int> np1(rp1.proxy());

// normal assignment:
rp1 = make_root<int>(1);

// the following will unify the new 'node_proxy' of 'rp2' with 'rp1' and 
// assign the pointer to 'rp1':
rp1 = make_node<int>(rp2, 2);

// the following will create a new 'root_ptr' but immediately slice it into a 'node_ptr' and 
// still use the 'node_proxy' of 'rp1':
np1 = make_root<int>(3);

// normal assignment:
np1 = make_node<int>(rp3, 4);
Cyclic function

In the case where a cyclic set is being destroyed, in order to prevent root_ptrs member pointers from accessing an object that has already been destroyed the function cyclic() is provided to explicitly check the state of the pointee object:

struct A
{
  root_ptr<A> p;

  void foo() {}

  ~A()
  {
    if (!p.cyclic())
    {
      p->foo();
    }
  }
};

root_ptr<A> p;
p = make_root<A>();
p->p = p;
[Warning] Warning

cyclic function should only be used in a destructor.

Faster pointees

Creating pointee objects in a faster way is possible by calling make_fastroot:

root_ptr<int> p = make_fastroot<int>(10);
Custom Allocator

You can actually use the allocator of your choice using the following function calls:

root_ptr<int> p = allocate_node<int>(make_node_allocator<fast_pool_allocator, int>(), 12);

Caveat

Hierarchies with multiple inheritance without virtual tables will cause undefined behavior if a pointer to a derived class is assigned to a pointer of one of its base class. For example:

struct M { int i; };
struct N { int i; };
struct O : N, M { int i; };

root_ptr<O> po = make_root<O>();
root_ptr<N> pn = po; // Incorrect!
root_ptr<M> pm = po; // Incorrect!

A way to bypass any problem that might be created by the example above is to add virtual destructors to the classes:

struct M { int i; virtual ~M() {} };
struct N { int i; virtual ~N() {} };
struct O : N, M { int i; };

root_ptr<O> po = make_root<O>();
root_ptr<N> pn = po; // Correct.
root_ptr<M> pm = po; // Correct.

Pitfall

It is important to point out that even if node_ptrs will get destroyed regardless cycles, it is possible to create a cyclic root_ptrs which will not behave as expected:

#include <iostream>
#include <boost/smart_ptr/root_ptr.hpp>

using namespace std;
using namespace boost;

struct A
{
    node_ptr<A> p;

    A(node_proxy const & x) : p(x)
    {
    }

    ~A()
    {
        cout << "~A()" << endl; // will get called
    }
};

struct B
{
    root_ptr<B> p;

    ~B()
    {
        cout << "~B()" << endl; // will not get called
    }
};

int main()
{
    { // 1:
        root_ptr<A> x;
        x = make_root<A>(x);
        x->p = x;
    } // deterministic destruction

    { // 2:
        root_ptr<B> x;
        x = make_root<B>();
        x->p = x;
    } // no destructor will be called

    return 0;
}

In the first occurrence of root_ptr<A> x, the node_proxy information is properly propagated and therefore the associated node_ptrs will be correctly detroyed.

In the second occurence of root_ptr<B> x, struct B is designed to accept a root_ptr which means it expects node_ptrs to be its children. If a root_ptr is assigned to another one, be aware that cycles will not be detected in this case and thus no destruction will occur in the aforementioned example.


PrevUpHomeNext