Boost Pointer Container Library

Examples

Some examples are given here and in the accompanying test files:

1. Null pointers cannot be stored in the containers

my_container.push_back( 0 );            // throws bad_ptr 
my_container.replace( an_iterator, 0 ); // throws bad_ptr
my_container.insert( an_iterator, 0 );  // throws bad_ptr                                                                 

2. Iterators and other operations return indirected values

ptr_vector<X> pvec; 
std::vector<X*> vec;
*vec.begin()  = new X;   // fine, memory leak
*pvec.begin() = new X;   // compile time error
( *vec.begin() )->foo(); // call X::foo(), a bit clumsy
pvec.begin()->foo();     // no indirection needed
*vec.front()  = X();     // overwrite first element
pvec.front()  = X();     // no indirection needed

3. Copy-semantics of pointer containers

ptr_vector<T> vec1; 
...
ptr_vector<T> vec2( vec1.clone() ); // deep copy objects of 'vec1' and use them to construct 'vec2', could be very expensive
vec2 = vec1.release();              // give up ownership of pointers in 'vec1' and pass the ownership to 'vec2', rather cheap
vec2.release();                     // give up ownership; the objects will be deallocated if not assigned to another container
vec1 = vec2;                        // compile time error: 'operator=()' not defined 
ptr_vector<T> vec3( vec1 );         // compile time error: copy-constructor not defined 

4. Making a non-copyable type Clonable

 // a class that has no normal copy semantics
class X : boost::noncopyable { public: X* clone() const; ... };
                                                                   
// this will be found by the library by argument dependent lookup                                                                   
X* allocate_clone( const X& x ) 
{ return x.clone(); }
                                                                   
// we can now use the interface that requires clonability
ptr_vector<X> vec1, vec2;
...
vec2 = vec1.clone();                                 // 'clone()' requires cloning <g> 
vec2.insert( vec2.end(), vec1.begin(), vec1.end() ); // inserting always means inserting clones 

5. Objects are cloned before insertion, inserted pointers are owned by the container

class X { ... };                     // assume 'X' is Clonable 
X x;                                 // and 'X' can be stack-allocated 
ptr_list<X> list; 
list.push_back( x );                 // clone 'x' and then insert the resulting pointer 
list.push_back( allocate_clone( x ); // do it manually
list.push_back( new X );             // always give the pointer directly to the container to avoid leaks
list.push_back( &x );                // don't do this!!! 

6. Transferring ownership of a single element

ptr_deque<T>                    deq; 
typedef ptr_deque<T>::auto_type auto_type;

// ... fill the container somehow

auto_type ptr  = deq.release_back();             // remove back element from container and give up ownership
auto_type ptr2 = deq.release( deq.begin() + 2 ); // use an iterator to determine the element to release
ptr            = deq.release_front();            // supported for 'ptr_list' and 'ptr_deque'

7. Transferring ownership of pointers between different pointer containers

ptr_list<X> list; ptr_vector<X> vec;
...
//
// note: no cloning happens in these examples                                
//
list.transfer( list.begin(), vec.begin(), vec );           // make the first element of 'vec' the first element of 'list'
vec.transfer( vec.end(), list.begin(), list.end(), list ); // put all the lists element into the vector                                 

8. Selected test files

incomplete_type_test.cpp:
 Shows how to implement the Composite pattern.
simple_test.cpp:
 Shows how the usage of pointer container compares with a container of pointer pointers
view_example.cpp:
 Shows how to use a pointer container as a view into other container
tree_test.cpp:Shows how to make a tree-structure
array_test.cpp:Shows how to make an n-ary tree

9. A large example

This examples shows many of the most common features at work.

//
// This example is intended to get you started.
// Notice how the smart container
//
// 1. takes ownership of objects
// 2. transfers ownership
// 3. applies indirection to iterators 
// 4. clones objects from other smart containers
// 

//
// First we select which container to use.
//
#include <boost/ptr_container/ptr_deque.hpp>

//
// we need these later in the example
//
#include <boost/assert.hpp>
#include <string>
#include <exception>


//
// Then we define a small polymorphic class
// hierarchy.
// 

class animal : boost::noncopyable
{
    virtual std::string do_speak() const = 0;
    std::string name_;

protected:
    //
    // Animals cannot be copied...
    //
    animal( const animal& r ) : name_( r.name_ )           { }
    void operator=( const animal& );

private:
    //
    // ...but due to advances in genetics, we can clone them!
    //

    virtual animal* do_clone() const = 0;
        
public:
    animal( const std::string& name ) : name_(name)        { }
    virtual ~animal() throw()                              { }
    
    std::string speak() const
    {
        return do_speak();
    }

    std::string name() const
    {
        return name_;
    }

    animal* clone() const
    {
        return do_clone();
    }
};

//
// An animal is still not Clonable. We need this last hook.
//
// Notice that we pass the animal by const reference
// and return by pointer.
//

animal* new_clone( const animal& a )
{
    return a.clone();
}

//
// We do not need to define 'delete_clone()' since
// since the default is to call the default 'operator delete()'.
//

const std::string muuuh = "Muuuh!";
const std::string oiink = "Oiiink";

class cow : public animal
{
    virtual std::string do_speak() const
    {
        return muuuh;
    }

    virtual animal* do_clone() const
    {
        return new cow( *this );
    }

public:
    cow( const std::string& name ) : animal(name)          { }
};

class pig : public animal
{
    virtual std::string do_speak() const
    {
        return oiink;
    }

    virtual animal* do_clone() const
    {
        return new pig( *this );
    }
    
public:
    pig( const std::string& name ) : animal(name)          { }
};

//
// Then we, of course, need a place to put all
// those animals.
//

class farm
{
    //
    // This is where the smart containers are handy
    //
    typedef boost::ptr_deque<animal> barn_type;
    barn_type                        barn;

    //
    // An error type
    //
    struct farm_trouble : public std::exception           { };

public:
    // 
    // We would like to make it possible to
    // iterate over the animals in the farm
    //
    typedef barn_type::iterator  animal_iterator;

    //
    // We also need to count the farm's size...
    //
    typedef barn_type::size_type size_type;
    
    //
    // And we also want to transfer an animal
    // safely around. The easiest way to think
    // about '::auto_type' is to imagine a simplified
    // 'std::auto_ptr<T>' ... this means you can expect
    // 
    //   T* operator->()
    //   T* release()
    //   deleting destructor
    //
    // but not more.
    //
    typedef barn_type::auto_type  animal_transport;

    // 
    // Create an empty farm.
    //
    farm()                                                 { }
    
    //
    // We need a constructor that can make a new
    // farm by cloning a range of animals.
    //
    farm( animal_iterator begin, animal_iterator end )
     : 
        //
        // Objects are always cloned before insertion
        // unless we explicitly add a pointer or 
        // use 'release()'. Therefore we actually
        // clone all animals in the range
        //
        barn( begin, end )                               { }
    
    //
    // ... so we need some other function too
    //

    animal_iterator begin()
    {
        return barn.begin();
    }

    animal_iterator end()
    {
        return barn.end();
    }
    
    //
    // Here it is quite ok to have an 'animal*' argument.
    // The smart container will handle all ownership
    // issues.
    //
    void buy_animal( animal* a )
    {
        barn.push_back( a );
    }

    //
    // The farm can also be in economical trouble and
    // therefore be in the need to sell animals.
    //
    animal_transport sell_animal( animal_iterator to_sell )
    {
        if( to_sell == end() )
            throw farm_trouble();

        //
        // Here we remove the animal from the barn,
        // but the animal is not deleted yet...it's
        // up to the buyer to decide what
        // to do with it.
        //
        return barn.release( to_sell );
    }

    //
    // How big a farm do we have?
    //
    size_type size() const
    {
        return barn.size();
    }

    //
    // If things are bad, we might choose to sell all animals :-(
    //
    std::auto_ptr<barn_type> sell_farm()
    {
        return barn.release();
    }

    //
    // However, if things are good, we might buy somebody
    // else's farm :-)
    //

    void buy_farm( std::auto_ptr<barn_type> other )
    {
        //
        // This line inserts all the animals from 'other'
        // and is guaranteed either to succeed or to have no
        // effect
        //
        barn.transfer( barn.end(), // insert new animals at the end
                         *other );     // we want to transfer all animals,
                                       // so we use the whole container as argument
        //
        // You might think you would have to do
        //
        // other.release();
        //
        // but '*other' is empty and can go out of scope as it wants
        //
        BOOST_ASSERT( other->empty() );
    }
    
}; // class 'farm'.

int main()
{
    //
    // First we make a farm
    //
    farm animal_farm;
    BOOST_ASSERT( animal_farm.size() == 0u );
    
    animal_farm.buy_animal( new pig("Betty") );
    animal_farm.buy_animal( new pig("Benny") );
    animal_farm.buy_animal( new pig("Jeltzin") );
    animal_farm.buy_animal( new cow("Hanz") );
    animal_farm.buy_animal( new cow("Mary") );
    animal_farm.buy_animal( new cow("Frederik") );
    BOOST_ASSERT( animal_farm.size() == 6u );

    //
    // Then we make another farm...it will actually contain
    // a clone of the other farm.
    //
    farm new_farm( animal_farm.begin(), animal_farm.end() );
    BOOST_ASSERT( new_farm.size() == 6u );

    //
    // Is it really clones in the new farm?
    //
    BOOST_ASSERT( new_farm.begin()->name() == "Betty" );
    
    //
    // Then we search for an animal, Mary (the Crown Princess of Denmark),
    // because we would like to buy her ...
    //
    typedef farm::animal_iterator iterator;
    iterator to_sell;
    for( iterator i   = animal_farm.begin(),
                  end = animal_farm.end();
         i != end; ++i )
    {
        if( i->name() == "Mary" )
        {
            to_sell = i;
            break;
        }
    }

    farm::animal_transport mary = animal_farm.sell_animal( to_sell );


    if( mary->speak() == muuuh )
        //
        // Great, Mary is a cow, and she may live longer
        //
        new_farm.buy_animal( mary.release() );
    else
        //
        // Then the animal would be destroyed (!)
        // when we go out of scope.
        //
        ;

    //
    // Now we can observe some changes to the two farms...
    //
    BOOST_ASSERT( animal_farm.size() == 5u );
    BOOST_ASSERT( new_farm.size()    == 7u );

    //
    // The new farm has however underestimated how much
    // it cost to feed Mary and its owner is forced to sell the farm...
    //
    animal_farm.buy_farm( new_farm.sell_farm() );

    BOOST_ASSERT( new_farm.size()    == 0u );
    BOOST_ASSERT( animal_farm.size() == 12u );     
}

10. Changing the Clone Allocator

This example shows how we can change the Clone Allocator to use the pointer containers as view into other containers:

//
// This example is intended to show you how to
// use the 'view_clone_manager'. The idea
// is that we have a container of non-polymorphic
// objects and want to keep then sorted by different
// criteria at the same time. 
//

//
// We'll go for 'ptr_vector' here. Using a node-based 
// container would be a waste of space here.
// All container headers will also include
// the Clone Managers.
// 
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/ptr_container/indirect_fun.hpp>

#include <functional> // For 'binary_fnuction'
#include <cstdlib>    // For 'rand()'
#include <algorithm>  // For 'std::sort()'
#include <iostream>   // For 'std::cout'

using namespace std;

//
// This is our simple example data-structure. It can
// be ordered in three ways.
//
struct photon
{
    photon() : color( rand() ), 
               direction( rand() ),
               power( rand() )
    { }
    
    int color;
    int direction;
    int power;
};

//
// Our big container is a standard vector
//
typedef std::vector<photon>                                 vector_type;

//
// Now we define our view type by adding a second template argument.
// The 'view_clone_manager' will implements Cloning by taking address
// of objects.
//
// Notice the first template argument is 'photon' and not
// 'const photon' to allow the view container write access.
//
typedef boost::ptr_vector<photon,boost::view_clone_allocator> view_type;

//
// Our first sort criterium
//
struct sort_by_color : std::binary_function<photon,photon,bool>
{
    bool operator()( const photon& l, const photon& r ) const
    {
        return l.color < r.color;
    }
};

//
// Our second sort criterium
//
struct sort_by_direction : std::binary_function<photon,photon,bool>
{
    bool operator()( const photon& l, const photon& r ) const
    {
        return l.direction < r.direction;
    }
};


//
// Our third sort criterium
//
struct sort_by_power : std::binary_function<photon,photon,bool>
{
    bool operator()( const photon& l, const photon& r ) const
    {
        return l.power < r.power;
    }
};

//
// This function inserts "Clones" into the
// the view. 
//
// We need to pass the first argument
// as a non-const reference to be able to store
// 'T*' instead of 'const T*' objects. Alternatively,
// we might change the declaration of the 'view_type'
// to 
//     typedef boost::ptr_vector<const photon,boost::view_clone_manager> 
//               view_type;     ^^^^^^
//
void insert( vector_type& from, view_type& to )
{
        to.insert( to.end(), 
                   from.begin(),
                   from.end() );
}

int main()
{
    enum { sz = 10, count = 500 };

    //
    // First we create the main container and two views
    //
    std::vector<vector_type>  photons;
    view_type                 color_view;
    view_type                 direction_view;

    //
    // Then we fill the main container with some random data
    //
    for( int i = 0; i != sz; ++i )
    {
        photons.push_back( vector_type() ); 

        for( int j = 0; j != count; ++j )
            photons[i].push_back( photon() );
    }

    //
    // Then we create the two views.
    //
    for( int i = 0; i != sz; ++i )
    {
        insert( photons[i], color_view );
        insert( photons[i], direction_view );
    }

    //
    // First we sort the original photons, using one of
    // the view classes. This may sound trivial, but consider that
    // the objects are scatered all around 'sz' different vectors;
    // the view makes them act as one big vector.
    //
    std::sort( color_view.begin(), color_view.end(), sort_by_power() );
    
    //
    // And now we can sort the views themselves. Notice how
    // we switch to different iterators and different predicates:
    //
    color_view.sort( sort_by_color() );

    direction_view.sort( sort_by_direction() );

    return 0;
}

Navigate:

copyright:Thorsten Ottosen 2004-2005.