Подтвердить что ты не робот

Почему С++ 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
}
4b9b3361

Ответ 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.