[cfe-dev] __vector_base::__destruct_at_end

Howard Hinnant hhinnant at apple.com
Wed Jun 26 07:59:57 PDT 2013


On Jun 26, 2013, at 10:31 AM, Shriramana Sharma <samjnaa at gmail.com> wrote:

> Hello. I've been examining the libc++ sources primarily to know about
> how containers are implemented, and I note this member function of
> __vector_base:
> 
> void __destruct_at_end(const_pointer __new_last, true_type) _NOEXCEPT;
> 
> Where is it used? In the same file I only observe calls to:
> 
> __destruct_at_end(const_pointer __new_last)
> 
> which delegates to:
> 
> __destruct_at_end(const_pointer __new_last, false_type)
> 
> Or have I missed something? Also, why the two functions?

This is an optimization that I found was not working, so I disabled it with the intent of pulling it out completely if the disabilization went well.  It has, I'll pull it out.  

> 
> Also: why separate all the containers into __container and
> __container_base with the former inheriting from the latter? Can't it
> be done in a single class?

This is a C++03 technique for making container constructors exception safe.  Let's look at an example:

template <class _Tp, class _Allocator>
template <class _InputIterator>
vector<_Tp, _Allocator>::vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a)
    : __base(__a)
{
    for (; __first != __last; ++__first)
        push_back(*__first);
}

I've removed the _InputIterator constraint, and the debugging support to focus the discussion.  If any of the push_back's throw an exception, this constructor must undo all previous push_back's.  This is the job of the base class.  The base class is completely constructed prior to the start of any of the push_back's.  So its destructor will run.  However the derived class's destructor (~vector()) won't run until vector's constructor completes.  I.e. without the base class running ~__vector_base() (which calls clear() and deallocate), this constructor would leak memory if an exception is thrown.

A C++03 alternative is to use try catch:

template <class _Tp, class _Allocator>
template <class _InputIterator>
vector<_Tp, _Allocator>::vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a)
   :  allocator_(__a)  // no base class, just store the allocator
{
    try
    {
        for (; __first != __last; ++__first)
            push_back(*__first);
    }
    catch (...)
    {
        clear();
        shrink_to_fit();
        throw;
    }
}

In C++11 there is an even better way.  However I don't use the following in libc++ because I want the library to work (more or less) in C++03 mode.  But for completeness, here it is:

template <class _Tp, class _Allocator>
template <class _InputIterator>
vector<_Tp, _Allocator>::vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a)
    : vector(__a)
{
    for (; __first != __last; ++__first)
        push_back(*__first);
}

This constructor forwards to the vector constructor that just takes an allocator.  Once that constructor is complete (all it does is store the allocator), then ~vector() will run if there is an exception in the "outer" constructor.

Howard




More information about the cfe-dev mailing list