__vector_base::__destruct_at_end

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?

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?

Thanks!

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

Hi and thank you very much for your reply.

void __destruct_at_end(const_pointer __new_last, true_type) _NOEXCEPT;
__destruct_at_end(const_pointer __new_last, false_type)

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.

So which of the two will remain? Do I presume the false_type version?
(And I presume the true_type/false_type thing is an idiom to avoid
creating a differently named delegate function? But a differently
named delegate may clarify the purpose of the segregation, no?)

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:

Wow very nice explanation, thank you very much! I much prefer the
try-catch model. Why isn't it then preferred in the libc++
implemented? IMO less convoluted, and would avoid lots of base::method
calls, no?

Thanks again!

Hi and thank you very much for your reply.

void __destruct_at_end(const_pointer __new_last, true_type) _NOEXCEPT;
__destruct_at_end(const_pointer __new_last, false_type)

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.

So which of the two will remain? Do I presume the false_type version?
(And I presume the true_type/false_type thing is an idiom to avoid
creating a differently named delegate function? But a differently
named delegate may clarify the purpose of the segregation, no?)

The false_type version will remain, folded into the __destruct_at_end(pointer __new_last). This technique is used to chose among multiple implementations at compile time, without instantiating the unchosen branches.

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:

Wow very nice explanation, thank you very much! I much prefer the
try-catch model. Why isn't it then preferred in the libc++
implemented? IMO less convoluted, and would avoid lots of base::method
calls, no?

In the base class design the clean up code goes in one place: ~base(). In the try-catch design the clean up code gets repeated in each constructor. One could shuffle the clean up code off to a single function, but one still has to replicate the try/catch(...) {clean_up();} for each constructor.

Howard

Oh, almost forgot. For the case that client code terminates with an uncaught exception, the base class design leaves a better stack trace on most OS's. If there are no try/catch(...) {throw;} in the unwinding, the stack trace starts from where the exception is thrown, otherwise it starts at the re-throw;.

Howard