Support for sequence in-place construction

Author: Thorsten Ottosen
Contact: thorsten.ottosen@dezide.com
organizations:Dezide Aps
Date: 2007-03-08
Number:WG21/N2212 and J16/07-0072
Working Group:Library

Table of Contents

1   Introduction

The lack of in-place construction in standard sequences is normally not a big problem. However, for libraries that tries to squeeze out the maximum efficiency of C++, it is a big problem that standard sequences are overly encapsulated. Experts needs to be able to write optimally efficient code.

The following type of libraries will benefit from this proposal:

Even though move-semantics will help performance, it is still not going to lead to optimal performance because construction followed by move-construction is less efficient than construction alone. Furthermore, some types are no more efficient to move than to copy, and those types are not only builtin types.

2   Examples

The following code demonstrates how one may exception-safely fill a generic sequence with value from a serialization library:

template< class Sequence, class Archive >
void read( Sequence& into, Archive& from )
{
    int size;
    from >> size;
    into.uninitialized_grow( size );
    
    try
    {
        typename Sequence::iterator e = into.begin(); 
        for( int i = 0; i != size; ++i, ++e )
        {
            typedef typename Sequence::value_type T;
            //
            // Call placement new directly on 
            // uninitialized buffer
            //
            new( &*e ) T( from );
        }
    }
    catch( ... ) 
    {
        into.uninitialized_shrink( size - i );
        throw;
    }
}

The following code demonstrates how one may trivially code the handy uninitialized_resize() function:

template< class Sequence >
void uninitialized_resize( Sequence& seq, typename Sequence::size_type n )
{
    if( n > seq.size() )
        seq.uninitialized_grow( n - seq.size() );
    else if( n < seq.size() )
        seq.uninitialized_shrink( seq.size() - n );
    else
        ;
    assert( seq.size() == n );
}                   

3   Design rationale

One could imagine different ways to achieve the same. For example, to fill an std::vector<T> with uninitialized storage, we could add the following member:

template <class T, class A>
class vector
{
   ...
   template <class F>  // F models functor(T* data, size_type n)
   vector(size_type n, F f);  // initialize vector with f(data(), n)
   ...
};

and then use it like:

struct dont_initialize
{
   template <class T>
   void operator()(T*, size_t) const {}
};

...

vector<double> v(1000, dont_initialize());

This has several drawbacks:

  1. there are already too many constructors in the container interface.
  1. it is harder and inconvenient to add in-place construction because the functor needs access to the data used for the in-place construction.
  1. it is overly restrictive to confine the uninitialized growth to construction.
  1. it is incomplete since exception-safe code needs a special way to erase uninitialized elements too.

Instead an approach similar to the existing insert() member functions has been pursued.

4   Wording

4.1   Synopsis

Add the following members to the synopsis of the class templates basic_string, list , deque and vector:

iterator uninitialized_grow( size_type n );
void     uninitialized_shrink( size_type n ); 

4.2   Specification

Add the following specification after the synopsis of the class templates basic_string, list , deque and vector:

iterator uninitialized_grow( size_type n );

  • Effects: increases the container's size with n by growing the container with uninitialized objects at the end.
  • Returns: an iterator r where &*r points to the first of n subsequent regions of uninitialized memory in the range [r,r+n) that can each be used to construct objects of type value_type in.
  • Remarks: if memory allocation fails, there are no effects.
  • Throws: length_error if size() + n > max_size().

void uninitialized_shrink( size_type n );

  • Effects: decreases the container's size by n by removing the n last regions of uninitialized memory without calling any destructor.
  • Precondition: at least n uninitialized objects exists at the end of the container.
  • Throws: Nothing

5   Acknowledgements

Many thanks to Christopher Kohlhoff and Matthias Troyer for their feedback.