Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why did C++11 remove the default value from the prototypes of std::vector's fill constructor?

Tags:

c++

c++11

vector

In C++98 the prototype for std::vector's fill constructor has a default value for the initializer.

explicit vector (size_type n, const value_type& val = value_type(),                  const allocator_type& alloc = allocator_type()); 

C++11 uses two prototypes.

explicit vector (size_type n);          vector (size_type n, const value_type& val,                  const allocator_type& alloc = allocator_type()); 

(In C++14 the fill constructor changed again, but it's not the point of this question.)

A reference link is here.

Why did C++11 deprecate the default initializer value value_type()?

By the way, I tried to compile the following code with clang++ -std=c++11 and it issued an error, which means the value type still needs to have a default constructor like S() {}, i.e. be default-constructible.

#include <vector>  struct S {     int k;     S(int k) : k(k) {} // intentionally remove the synthesized default constructor };  int main() {     std::vector<S> s(5); // error: no matching constructor } 
like image 454
Leedehai Avatar asked Aug 28 '17 19:08

Leedehai


People also ask

What is the default value of vector in C++?

The default value of a vector is 0.

Does a vector have a default constructor?

The default vector constructor takes no arguments, creates a new instance of that vector. The second constructor is a default copy constructor that can be used to create a new vector that is a copy of the given vector c. All of these constructors run in linear time except the first, which runs in constant time.

What happens when a vector goes out of scope?

In this case, all the elements are deleted, but the name of the vector is not deleted. The second way to delete a vector is just to let it go out of scope. Normally, any non-static object declared in a scope dies when it goes out of scope. This means that the object cannot be accessed in a nesting scope (block).


2 Answers

The C++98 took a prototype object, then copied it n times. By default the prototype was a default-constructed object.

The C++11 version constructs n default-constructed objects.

This eliminates n copies and replaces it with n default-constructions. In addition, it avoids constructing the prototype.

Suppose your class looks like this:

struct bulky {   std::vector<int> v;   bulky():v(1000) {} // 1000 ints   bulky(bulky const&)=default;   bulky& operator=(bulky const&)=default;    // in C++11, avoid ever having an empty vector to maintain   // invariants:   bulky(bulky&& o):bulky() {     std::swap(v, o.v);   }   bulky& operator=(bulky&& o) {     std::swap(v,o.v);     return *this;   } }; 

this is a class that always owns a buffer of 1000 ints.

if we then create a vector of bulky:

std::vector<bulky> v(2); 

in C++98 this allocated 3 times 1000 integers. In C++11 this allocated only 2 times 1000 integers.

In addition, the C++98 version requires that the type be copyable. There are non-copyable types in C++11, such as std::unique_ptr<T>, and a vector of default-constructed unique pointers cannot be generated using the C++98 signature. The C++11 signature has no problem with it.

std::vector<std::unique_ptr<int>> v(100); 

The above wouldn't work if we still had the C++98 version.

like image 187
Yakk - Adam Nevraumont Avatar answered Sep 18 '22 05:09

Yakk - Adam Nevraumont


The reason the constructor was split in two was to support "move-only" types such as unique_ptr<T>.

This constructor:

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

requires T to be copy constructible, because n Ts must be copied from value to populate the vector.

This constructor:

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

does not require T to be copy constructible, only default constructible.

The latter constructor works with unique_ptr<T>:

std::vector<std::unique_ptr<int>> s(5); 

while the former constructor does not.

Here is the proposal that made this change: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1858.html#23.2.4.1%20-%20vector%20constructors,%20copy,%20and%20assignment

And this paper has some of the rationale, though is admittedly a little on the terse side: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html

Fwiw, resize:

void resize(size_type sz, T c = T()); 

was split into:

void resize(size_type sz); void resize(size_type sz, const T& c); 

for the exact same reason. The first requires default constructible but not copy constructible (to support default constructible move-only types), and the second requires copy constructible.

These changes were not 100% backwards compatible. For some types (e.g. reference counted smart pointers), copy constructing from a default constructed object is not the same as default construction. However the benefit of supporting move-only types was judged to be worth the cost of this API breakage.

like image 31
Howard Hinnant Avatar answered Sep 22 '22 05:09

Howard Hinnant