Вектор структур с константными членами?

Скажем, у меня

#include <string>
#include <vector>
using namespace std;

struct Student
{
    const string name;
    int grade;
    Student(const string &name) : name(name) { }
};

Как я могу сохранить вектор студентов?

int main()
{
    vector<Student> v;

    // error C2582: 'operator =' function is unavailable in 'Student'
    v.push_back(Student("john"));
}

Есть ли способ сделать это, или я должен выделить всех студентов в куче и вместо этого сохранить указатель на каждый из них?

Ответ 1

Вы не можете. Ваш тип нарушает требование "Назначение" для стандартных контейнеров.

ISO/IEC 14882: 2003 23.1 [lib.container.requirements]/3:

Тип объектов, хранящихся в этих компонентах, должен соответствовать требованиям CopyConstructibleтипы (20.1.3) и дополнительные требования к типам Assignable.

Из таблицы 64 (Assignable требования):

В таблице 64 T - тип, используемый для создания экземпляра контейнера, T - значение T, а u - значение (возможно, const) T.

выражение: t = u; тип возврата: T; пост-условие: T эквивалентно u

Теоретически эквивалент a std::vector может во всех случаях делать разрушение и копировать конструкцию, но это не тот контракт, который был выбран. Если перераспределение не требуется, то использование указанного оператора присваивания типов для таких вещей, как vector::operator= и vector::assign, может быть значительно более эффективным.

Ответ 2

Простой ответ: вы не можете. Если у вас есть переменные-члены const, то компилятор не может предоставить оператор присваивания по умолчанию. Тем не менее, многие из операций, которые std::vector обеспечивают, требуют выполнения назначений и, следовательно, требуют (публичного) оператора присваивания копий.

Ваши варианты:

  • Сделайте name не const.
  • Напишите свой собственный оператор присваивания копий и подумайте о способе работы с "копированием" члена const.

Ответ 3

A vector часто нужно перемещать элементы вокруг. Каждый раз, когда вектор должен расти, когда вы вызываете push_back(), он перераспределяет память, чтобы поддерживать непрерывность, и копирует все существующие элементы в новое пространство. Также, если вы вызываете insert() или remove(), элементы должны смещаться. Для vector, чтобы иметь возможность делать все, что элементы должны быть назначены для копирования, а это означает, что тип, который вы храните в векторе, должен иметь определенный оператор присваивания.

В общем случае, если вы определяете класс, компилятор сгенерирует для вас оператор присваивания для этого класса. Однако есть случаи, когда компилятор не может этого сделать. Один из таких случаев - когда класс имеет постоянные члены (обратите внимание, что указатели на константу в порядке).

Итак, в вашем случае проблема заключается в const string name. Он не позволяет компилятору генерировать operator=(), что, в свою очередь, предотвращает компиляцию vector, даже если вы фактически не используете назначение на своих элементах самостоятельно.

Одно из решений состоит в том, чтобы сделать name неконстантным. Другой - написать свой собственный Student::operator=(), каким-то образом это имеет смысл. Третий способ, как вы указали, - использовать вектор указателей, а не вектор объектов. Но тогда вы должны обрабатывать их распределение и выделение.

P.S. Другой случай, когда компилятор не может сгенерировать operator=, - это когда ваш класс имеет члены, которые являются ссылками.

Ответ 4

Элементы векторов должны быть назначены для копирования, а ваша структура Student не из-за члена const. Просто используйте string name вместо const string name. Если у вас нет конкретного требования, постоянные члены в классах редко бывают полезны. Если вы хотите предотвратить изменения в члене, сделайте его закрытым и добавьте открытую функцию getter.