Почему элементам std::vector не нужен конструктор по умолчанию?

И как я могу написать свой собственный класс массива, чтобы не нуждаться в конструкторе по умолчанию для его элементов? Прямо сейчас, когда я делаю новый [] для выделения места, мне нужен конструктор по умолчанию.

std::vector — нет.

Как они делают это волшебство?


person anon    schedule 04.03.2010    source источник


Ответы (4)


std::vector не нужен конструктор по умолчанию, потому что он никогда не использует его. Каждый раз, когда ему нужно построить элемент, он делает это с помощью конструктора копирования, потому что каждый раз ему есть что копировать: либо существующий элемент вектора, либо элемент, который вы сами предоставили для копирования через параметр метода. (явно или неявно, полагаясь на аргумент по умолчанию)

Вы можете написать такой класс точно таким же образом: каждый раз, когда вам нужно построить новый элемент в вашем массиве, требуйте от пользователя предоставления элемента для копирования. В этом случае создание исходного элемента становится обязанностью пользователя.

Каждый раз, когда кажется, что std::vector "требует" от вас конструктор по умолчанию, это просто означает, что где-то вы полагались на аргумент по умолчанию некоторых методов vectors, то есть это вы пытались использовать по умолчанию- построить элемент, а не вектор. Сам вектор, опять же, никогда не будет пытаться создавать элементы по умолчанию.

Чтобы избежать требования конструктора по умолчанию при выделении памяти, стандартная библиотека выделяет необработанный неинициализированный блок памяти, а затем немедленно копирует-конструирует новые элементы в этом необработанном блоке памяти (чего new[] не может сделать). Эта функциональность инкапсулирована в классе std::allocator. Вы также можете использовать std::allocator в своем коде, а это означает, что «магия» сразу же доступна и вам.

Примечание. Вышеизложенное относится к версии спецификации языка C++ до C++11. C++11 многое изменил. И эти изменения действительно создают ситуации, в которых std::vector может использовать конструкторы по умолчанию внутри.

Также стоит отметить, что даже исходная спецификация C++98 позволяла реализациям использовать перегрузку функций вместо аргументов по умолчанию для реализации интерфейса стандартной библиотеки. Это означает, что формально возможно иметь допустимую реализацию std::vector на C++98, которая использует конструкторы по умолчанию внутренне.

person AnT    schedule 04.03.2010
comment
с другой стороны, они используют некоторую магию, которая очень связана: объект std::allocator. - person Mooing Duck; 21.04.2012
comment
std::vector::emplace_back() вызывает конструктор по умолчанию. Обычно emplace_back(Args...) вызывает конструктор с аргументами Args... - person Tim Kuipers; 13.08.2015
comment
@Angelorf этот ответ был написан до того, как был выпущен С++ 11 :) На самом деле С++ 11 изменил поведение vector<X> x(5) - теперь это указано как вызов конструктора по умолчанию на месте 5 раз, тогда как в С++ 03 это означало что вы по умолчанию создаете один X, а затем вектор использует копирование-конструкцию 5 раз, после чего ваше значение по умолчанию уничтожается. - person M.M; 21.12.2015

std::vector требует, чтобы элемент имел конструктор по умолчанию только в том случае, если вы используете его таким образом, который требует конструктора по умолчанию. Таким образом, этот код (украденный из удаленного ответа) не будет компилироваться, потому что X не имеет ctor по умолчанию:

#include <vector>

struct X
{
  X(int) {}
};

int main(void)
{
  std::vector<X> x(1); // vector of length 1, second argument defaults to X() !!
  return 0;
}

Но если вместо этого вы напишете main вот так:

int main(void)
{
  std::vector<X> x; // make empty vector
  x.push_back(X(1));
  return 0;
}

Тогда он работает нормально.

person Dan    schedule 04.03.2010
comment
Первая версия не скомпилируется, потому что std::vector<X> x(1) — это сокращение от std::vector<X> x(1, X()). На самом деле именно вы косвенно используете конструктор по умолчанию, а не vector. Аргументы по умолчанию оцениваются на вашей стороне. - person AnT; 04.03.2010

Вы можете выделить блок байтов, а затем использовать размещение new, чтобы создать новый экземпляр T ( ваш параметрический тип) через конструктор копирования (конечно, не конструктор по умолчанию), когда новые элементы помещаются в конец вектора. Это не позволит создать "вектор из N инициализированных по умолчанию T" (что может сделать std::vector - вот почему для этой цели действительно T должен иметь конструктор по умолчанию), но вы можете сделать векторы, которые начинаются пустыми, и на них можно нажимать T.

person Alex Martelli    schedule 04.03.2010
comment
Как мне выделить место в первую очередь? маллок? - person anon; 04.03.2010
comment
@anon: Посмотрите, как это делает vector... он использует распределитель, например new_allocator. В моей (старой) установке Cygwin это работает так: { return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); } Или malloc_allocator делает это так: pointer __ret = static_cast<_Tp*>(malloc(__n * sizeof(_Tp))); - person Dan; 04.03.2010

Для меня std::vector требовался конструктор по умолчанию для моего класса (скажем, T), потому что я вызывал resize() метод vector, несмотря на то, что я вызывал метод только для сжатия вектора, а не для его увеличения.

person Serge Rogatch    schedule 21.06.2020