Std::Vector, Default Construction, C++11 and Breaking Changes

std::vector, default construction, C++11 and breaking changes

Does the C++03 standard mandate that std::vector must have a constructor defined as above, i.e. with a default argument? In particular is there a guarantee that the entries of the vector object get copied instead of default constructed?

Yes, the specified behavior is that x is copied n times so that the container is initialized to contain with n elements that are all copies of x.


What does the C++11 Standard say about this same point?

In C++11 this constructor has been turned into two constructors.

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n); // (2)

Except for the fact that it no longer has a default argument for the second parameter, (1) works the same way as it does in C++03: x is copied n times.

In lieu of the default argument for x, (2) has been added. This constructor value-initializes n elements in the container. No copies are made.

If you require the old behavior, you can ensure that (1) is called by providing a second argument to the constructor invocation:

std::vector<S> v(42, S());

I see this as a possibility for a breaking change between C++03 and C++11. I see this as a possibility for a breaking change between C++03 and C++11. Has this issue been investigated? Solved?

Yes, as your example demonstrates, this is indeed a breaking change.

As I am not a member of the C++ standardization committee (and I haven't paid particularly close attention to library-related papers in the mailings), I don't know to what degree this breaking change was discussed.

Is it guaranteed that std::vector default construction does not call new?

Does this guarantee that there is no dynamic memory allocation?

No. It is however quite typical that an implementation doesn't allocate memory. I haven't seen a standard library implementation that does.

Or may an implementation chose to reserve some memory?

It may, but that's atypical.

I known that, for this empty constructor, there is no construction of the type T since C++11

Also prior to C++11.

What is 2D vector construction breaking change in C++11?

I emailed Stephan and asked what he was talking about. Here's his answer (edited for formatting). It didn't sound like he was planning to post the answer here; if he does, I'll delete this copy.

Everything from here down is Stephan speaking.

I was referring to this:

#include <vector>
using namespace std;

int main() {
vector<vector<int>> v(11, 22);
}

It compiles with VC10 SP1 (following C++03), but not with VC11 RTM (following C++11): [snip error message dump]

C++03 23.1.1 [lib.sequence.reqmts]/9 said:

For every sequence defined in this clause and in clause 21:

— the constructor

template <class InputIterator> X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
shall have the same effect
as:

X(static_cast<typename X::size_type>(f), static_cast<typename X::value_type>(l), a)
if InputIterator is an integral type.

This transformed vector<vector<int>> v(11, 22) to vector<vector<int>> v(static_cast<size_t>(11), static_cast<vector<int>>(22)), which is valid. (static_cast is capable of invoking explicit constructors, like vector's size constructor.)

C++11 23.2.3 [sequence.reqmts]/14 says:

For every sequence container defined in this Clause and in Clause 21:

— If the constructor

template <class InputIterator> X(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type())
is called with a type InputIterator that does not qualify as an input iterator, then the constructor shall not participate in overload resolution.

This removes the (InIt, InIt) ctor from overload resolution. That leaves (size_type n, const T& value), where T is vector<int>. However, that would try to implicitly convert 22 to a temporary vector<int> (in order to bind it to const T&). The explicitness of vector's size constructor forbids that.

Reading the other SO question, this is a different issue.

STL

Why was the constructor interface of std::vector changed with C++11?

Before, when you wrote std::vector<T> buf(100); you would get one T default constructed, and then that instance would be copied over to one hundred slots in the vector.

Now, when you write std::vector<T> buf(100);, it will use another constructor: explicit vector( size_type count );. This will default-construct one hundred Ts. It's a slight difference, but an important one.

The new single-argument constructor doesn't require the type T to be copyable. This is important because now types can be movable and not copyable.

Default value of std::vector for base types

The standard stipulates that resize will value-initialize any items that need to be inserted, so the behavior here is perfectly well defined.

In simple terms, value-initialization means:

  • for class types, the default constructor is called; if no constructor has been provided, then each member is value-initialized (recursively)
  • for scalars of any type T, they are given the value (T)0 (this guarantees that if T is a pointer the value used will be null portably)

Why does an empty vector call the value type's default constructor?

Because you're explicitly passing an initial size, which calls a constructor that has another parameter whose default value is s(). Just leave out the (0) (i.e. std::vector<s> v;) and it won't happen.

For completeness, the Standard 23.2.4-2 defines the constructor you're calling as:

    explicit vector(size_type n, const T& value =T(),
                                    const Allocator& = Allocator());

Aside (relevant to C++03 but not C++11)

Another interesting behavioural aspect of this constructor also raises its head on S.O. periodically: when the initial number of elements requested is > 0, it copy-constructs those elements from the prototypal parameter to the constructor:

  • people often put a default constructor that leaves member variables uninitialised, hoping to make vector(n) almost as fast as the underlying free store allocation, BUT
  • the copy-constructor is still called n times to copy the "garbage" content of the prototypal object into each of the requested elements

This has an obvious performance cost, but can also crash the application if the garbage content includes e.g. pointers that the copy-constructor can only assume are valid. Similarly, it's extremely dangerous to even push_back such an uninitialised garbage object - it lacks proper value semantic encapsulation and may be copied as the vector resizes, algorithmic operations like std::sort() are performed on the vector etc..

Reducing the size of a std::vector without a default constructor

In my implementation, it looks like we have (with a few simplifications):

void std::vector<T,A>::resize(size_type __new_size)
{
if (__new_size > size())
_M_default_append(__new_size - size());
else if (__new_size < size())
_M_erase_at_end(begin() + __new_size);
}

auto std::vector<T,A>::erase(iterator __first, iterator __last) -> iterator
{
if (__first != __last)
{
if (__last != end())
_GLIBCXX_MOVE3(__last, end(), __first);
_M_erase_at_end(__first + (end() - __last));
}
return __first;
}

where _M_... are private member functions. You really want the effects of _M_erase_at_end. I would guess it would be hard or impossible for a compiler to optimize the _M_default_append call out of v.resize(sz), but relatively easy to notice in v.erase(iter, v.end()) that __last == end() and optimize away the _GLIBCXX_MOVE3 and the + (end() - __last). So erase() could very well be more efficient than resize() here.

I would expect most implementations to be a similar story: a few simple if tests, and then calling some identical method to call destructors for elements at the end.



Related Topics



Leave a reply



Submit