требования к типу для std :: vector ‹type›

Я все еще не понимаю требований к типу, который будет использоваться с std::vector в C ++ 11, но это может быть вызвано ошибкой компилятора (gcc 4.7.0). Этот код:

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

работает нормально и дает ожидаемый результат, указывая, что вызывается ctor по умолчанию (явно указанный) (а не неявный ctor копирования). Однако, если я добавлю в класс удаленную копию ctor, а именно

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A const&) = delete;
  int X;
};

gcc 4.7.0 не компилируется, но пытается использовать удаленный ctor. Это правильное поведение или ошибка? Если первое, как заставить код работать?


person Walter    schedule 03.09.2012    source источник
comment
Пожалуйста, уточните ожидаемый результат. Сколько строк вывода получается?   -  person Ben Voigt    schedule 03.09.2012


Ответы (5)


Стандарт C ++ 11 действительно требует CopyInsertable, как указывали другие. Однако это ошибка стандарта C ++ 11. С тех пор это было исправлено в N3376 в MoveInsertable и DefaultInsertable.

Функция-член vector<T, A>::resize(n) требует MoveInsertable и DefaultInsertable. Они примерно переводятся в DefaultConstructible и MoveConstructible, когда распределитель A использует определения construct по умолчанию.

Следующая программа компилируется с использованием clang / libc ++:

#include <vector>
#include <iostream>

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A&&) = default;
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

и мне распечатывает:

 A::A(); this=0x7fcd634000e0
 A::A(); this=0x7fcd634000e4
 A::A(); this=0x7fcd634000e8
 A::A(); this=0x7fcd634000ec

Если вы удалите конструктор перемещения выше и замените его конструктором удаленной копии, A больше не будет _14 _ / _ 15_ как конструкция перемещения, а затем попытается использовать конструктор удаленной копии, как правильно показано в вопросе OP.

person Howard Hinnant    schedule 03.09.2012
comment
Однако обратите внимание, что libstdc ++ не принимает во внимание аргумент шаблона распределителя; смотри мой ответ. - person ecatmur; 04.09.2012
comment
У меня был тот же результат, но я все еще запутался, поскольку ctor по умолчанию вызывается для каждого объекта. Таким образом, move ctor, похоже, вообще не вызывается. Обратите внимание, что результат остается тем же самым, если ctor перемещения заменяется на ctor копирования: он никогда не вызывается, но требуется реализацией std::vector::resize(). - person Walter; 04.09.2012
comment
Я поддержал правильный ответ ecatmur на ваш вопрос на странице stackoverflow.com/a/12259886/576911 - person Howard Hinnant; 04.09.2012
comment
Я подозреваю, что отсутствие ctor по умолчанию - это нормально, если вы избегаете вызовов resize(). Также обратите внимание, что необходимо предоставить ctor копирования, если вы планируете использовать конструктор векторной заливки: std::vector<A> a(10,A());. - person DarioP; 20.10.2020

В C ++ 11 требования зависят от выполняемых операций. В случае std::vector<T>::resize() требование к T состоит в том, чтобы он был CopyInsertable в вектор.

Из §23.3.6.3

void resize(size_type sz);

....

Требуется: T должно быть CopyInsertable в * this.

person juanchopanza    schedule 03.09.2012

В ideone я вижу один вызов конструктора по умолчанию. Но четыре объекта создаются, остальные надо как-то строить. Фактически, объект-прототип создается по умолчанию, а затем копируется четыре раза.

Стандарт C ++ 11 (раздел 23.3.6.3) говорит, что будут вставлены объекты, инициализированные значением, но также требует, чтобы тип был копируемым:

void resize(size_type sz);

  • Эффекты: Если sz <= size(), эквивалентно erase(begin() + sz, end());. Если size() < sz, добавляет в последовательность sz - size() элементов, инициализированных значением.
  • Требуется: T должно быть CopyInsertable в *this.

Здесь нет ошибки компилятора; это ваш код неправильный.

person Ben Voigt    schedule 03.09.2012
comment
Бен, что интересно, мой вывод отличается от того, который вы получили с помощью gcc 4.5.4 на ideone: я получаю: A::A() on this=0x603010 A::A() on this=0x603014 A::A() on this=0x603018 A::A() on this=0x60301c, т.е. ctor по умолчанию вызывается 4 раза, а не только один раз, а затем объекты копируются из него. Это существенная разница ... - person Walter; 03.09.2012
comment
Однако это может быть дефект спецификации. Обратите внимание, что конструктору vector, который принимает только размер, требуется только DefaultConstructible. У resize нет причин требовать CopyInsertable; только DefaultConstructible и любое перераспределение, которое может потребоваться. - person Nicol Bolas; 03.09.2012
comment
@NicolBolas Значит, вы намекаете, что gcc глючит? - person Walter; 03.09.2012
comment
@Walter: дефект спецификации не является ошибкой в ​​программном обеспечении. Это ошибка в спецификации. - person Nicol Bolas; 03.09.2012

Чтобы использовать класс в векторе, он должен иметь конструктор копирования / оператор присваивания или конструктор перемещения / оператор присваивания noexcept. GCC совершенно правильно не компилировать ваш пример, в котором ничего из этого нет.

Как бы вы кроме вектора сделали что-либо, не имея возможности копировать или перемещать то, что он содержит?

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

person Dirk Holsopple    schedule 03.09.2012
comment
На самом деле это неправильно. Удаление конструктора копирования не должно влиять на оператор присваивания, а класс имеет определяемый пользователем конструктор по умолчанию. - person juanchopanza; 03.09.2012
comment
+1 за упоминание движка. Действительно, если я даю это (используя =default), но delete ctor копирования, он работает правильно! ИЗМЕНИТЬ Я интерпретирую ваш оператор присваивания как конструктор, чтобы исправить вашу ошибку. - person Walter; 03.09.2012
comment
@juanchopanza, удаляя копию ctor, предотвращает создание оператора присваивания перемещения. Он оставляет оператор присваивания копии по устаревшим причинам, но любая программа, которая его использует, вызывает большие подозрения. - person Dirk Holsopple; 03.09.2012
comment
Но ваше утверждение о том, что вы не получаете автоматически сгенерированные операторы присваивания, неверно. Вы получаете оператор присваивания копии, независимо от того, имеет ли он смысл. - person juanchopanza; 04.09.2012

void resize(size_type) требует CopyInsertable, что означает, что распределитель должен иметь возможность создавать-копировать тип:

::new((void*)p)A(A());

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

struct Allocator: public std::allocator<A> {
  void construct(A *, const A &) { }
};

Однако libstdc ++ этого не уважает; см. Должен (в C ++ 11 ) std :: vector :: resize (size_type) работает для конструктивного типа value_type по умолчанию int [4]?

person ecatmur    schedule 03.09.2012
comment
Почему не ::new((void*)p)A(); вместо ::new((void*)p)A(A());, я не понимаю преимущества A(A()) над _4 _... - person PiotrNycz; 03.09.2012
comment
@PiotrNycz из стандартной реализации iterator_traits::construct для создания копии. - person ecatmur; 04.09.2012