Почему С++ 11 удалил значение по умолчанию из прототипов конструктора заполнения std::vector?
В С++ 98 прототип конструктора заполнения std::vector
имеет значение по умолчанию для инициализатора.
explicit vector (size_type n, const value_type& val = value_type(),
const allocator_type& alloc = allocator_type());
С++ 11 использует два прототипа.
explicit vector (size_type n);
vector (size_type n, const value_type& val,
const allocator_type& alloc = allocator_type());
(В С++ 14 конструктор заполнения снова изменился, но это не точка этого вопроса.)
Ссылка здесь здесь.
Почему С++ 11 отказался от значения инициализатора по умолчанию value_type()
?
Кстати, я попытался скомпилировать следующий код с clang++ -std=c++11
, и он выпустил ошибку, а это значит, что тип значения по-прежнему должен иметь конструктор по умолчанию, например S() {}
, т.е. конструктивен по умолчанию.
#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
}
Ответы
Ответ 1
С++ 98 взял объект-прототип, а затем скопировал его n раз. По умолчанию прототип был построенным по умолчанию объектом.
В версии С++ 11 строятся n построенных по умолчанию объектов.
Это исключает n копий и заменяет их на n конструкциями по умолчанию. Кроме того, он избегает построения прототипа.
Предположим, что ваш класс выглядит следующим образом:
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;
}
};
это класс, который всегда имеет буфер 1000
int
s.
если мы создадим вектор bulky
:
std::vector<bulky> v(2);
в С++ 98 выделено 3 раза 1000 целых чисел. В С++ 11 это выделяет всего 2 раза 1000 целых чисел.
Кроме того, версия С++ 98 требует, чтобы тип был скопирован. В С++ 11 есть неконвертируемые типы, такие как std::unique_ptr<T>
, а vector
построенных по умолчанию уникальных указателей не могут быть сгенерированы с использованием подписи С++ 98. Подпись С++ 11 не имеет проблем с ней.
std::vector<std::unique_ptr<int>> v(100);
Вышеупомянутое не будет работать, если у нас все еще была версия С++ 98.
Ответ 2
Причина, по которой конструктор был разделен на две части, состоял в том, чтобы поддерживать типы "только для перемещения", такие как unique_ptr<T>
.
Этот конструктор:
vector(size_type n, const T& value, const Allocator& = Allocator());
требует, чтобы T
копировался конструктивно, потому что n
T
должен быть скопирован из value
, чтобы заполнить vector
.
Этот конструктор:
explicit vector(size_type n, const Allocator& = Allocator());
не требует, чтобы T
копировался конструктивно, только конструктив по умолчанию.
Последний конструктор работает с unique_ptr<T>
:
std::vector<std::unique_ptr<int>> s(5);
в то время как прежний конструктор не делает.
Вот предложение, которое произвело это изменение: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1858.html#23.2.4.1%20-%20vector%20constructors,%20copy,%20and%20assignment
И эта статья имеет некоторые обоснования, хотя, по общему признанию, немного на короткой стороне: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html
Fwiw, resize
:
void resize(size_type sz, T c = T());
разделили на:
void resize(size_type sz);
void resize(size_type sz, const T& c);
по той же самой причине. Первый требует по умолчанию конструктивного, но не скопированного конструктивного (для поддержки стандартных конструктивных типов только для перемещения), а для второго требуется копировать конструкцию.
Эти изменения не были совместимы на 100% назад. Для некоторых типов (например, подсчитанных ссылок на интеллектуальные указатели) копирование с построенного по умолчанию объекта не совпадает с конструкцией по умолчанию. Однако преимущество поддержки типов только для перемещения было оценено как стоимость этого разрыва API.