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)
ifInputIterator
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 typeInputIterator
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 T
s. 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
C++: Is It Safe to Cast Pointer to Int and Later Back to Pointer Again
Building Multiple Binaries Within One Eclipse Project
Std::Vector, Default Construction, C++11 and Breaking Changes
Why Does Same_As Concept Check Type Equality Twice
Tell Gdb to Skip Standard Files
Pointer to Array with Const Qualifier in C & C++
Undefined Reference Error for Template Method
Placement New and Assignment of Class with Const Member
Is the Backslash Acceptable in C and C++ #Include Directives
Parameter Pack Must Be at the End of the Parameter List... When and Why
Splitting a String into an Array in C++ Without Using Vector
How to Properly Uninitialize Openssl
Why Is Std::Unordered_Map Slow, and How to Use It More Effectively to Alleviate That
What Is an In-Place Constructor in C++
Why Does VS Not Define the Alternative Tokens for Logical Operators