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

Действительность указателей на внутреннюю структуру данных при перераспределении объекта std::vector

Предположим, что существует класс A, содержащий вектор ints. Предположим теперь, что вектор A создан. Если происходит перераспределение объекта A (поэтому векторный объект перемещается) из-за push_back, например, будут ли указатели на сами ints действительными? Гарантировано ли это?

Чтобы уточнить:

class A {
public:
    A() {};
    std::vector<int> a = {1,2,3,4,5,6,7,8,9};
};

int main()
{
    std::vector<A> aVec(2);

    int *x1 = &(aVec[1].a[2]);
    A *x2 = &aVec[1];
    std::cout << x1 << " - " << x2 << " - " << aVec.capacity() << "\n";

    aVec.resize(30);

    int *y1 = &(aVec[1].a[2]);
    A *y2 = &aVec[1];
    std::cout << y1 << " - " << y2 << " - " << aVec.capacity() << "\n";
}

Запуск этого кода дает следующее:

0x1810088 - 0x1810028 - 2
0x1810088 - 0x18100c8 - 30

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

Я посмотрел здесь [правила аннулирования Iterator], но не рассматривает этот конкретный случай (например, перераспределение самого векторного объекта).

UPDATE:

Я попробовал это, чтобы проверить, что я написал в комментариях для ответа Jarod42:

std::vector<std::vector<int>> aVec(2, {1,2,3});

int *x1 = &(aVec[1][2]);
std::vector<int> *x2 = &aVec[1];
std::cout << x1 << " - " << x2 << " - " << aVec.capacity() << "\n";

aVec.resize(30);

int *y1 = &(aVec[1][2]);
std::vector<int> *y2 = &aVec[1];
std::cout << y1 << " - " << y2 << " - " << aVec.capacity() << "\n";

и получил следующее:

0x16f0098 - 0x16f0048 - 2
0x16f0098 - 0x16f00c8 - 30

что странно для меня. Я ожидал x2 == y2.

4b9b3361

Ответ 1

К сожалению, это не гарантируется. Тем не менее, это означает, что все 3 текущих реализации (libС++, libstdС++ и VS-2015) гарантируют это. Вопрос в том, не является ли конструктором перемещения для A:

static_assert(std::is_nothrow_move_constructible<A>::value, "");

Конструктор перемещения для A является компилятором и, следовательно, зависит от конструктора перемещения std::vector<int>. Если конструктор перемещения std::vector<int> не является исключением, то конструктор перемещения для A не является исключением, иначе это не так.

Текущий проект N4296 не помещает конструктор перемещения для vector как noexcept. Однако это позволяет реализациям делать это.

Эта строка:

aVec.resize(30);

Будет использовать конструктор move A, если этот конструктор перемещения не существует, иначе он будет использовать конструктор A copy. Если он использует конструктор A copy, местоположение ints изменится. Если он использует конструктор перемещения A, расположение int будет оставаться стабильным.

libС++ и libstdС++ mark vector переместить конструктор как noexcept. И, таким образом, дайте A конструктор move noexcept.

VS-2015 говорит, что A не имеет конструктора move noexcept:

static_assert(std::is_nothrow_move_constructible<A>::value, "");

не компилируется.

Тем не менее, VS-2015 не перераспределяет int на новый адрес, и, похоже, он не соответствует спецификации С++ 11.

Если кто-то изменяет заголовки libС++, так что конструктор перемещения vector не помечен noexcept, тогда ints действительно перераспределяются.

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

static_assert(std::is_nothrow_move_constructible<A>::value, "");

компилируется, тогда у вас есть гарантия, иначе вы этого не сделаете.

Обновление

Причина, по которой x2 != y2 в обновлении заключается в том, что это адреса vector<int> в vector<vector<int>>. Этим внутренним элементам пришлось найти новый (больший) буфер, в котором можно было бы жить, точно так же, как если бы внутренний элемент был int. Но в отличие от int, внутренний элемент vector<int> мог перемещаться туда с помощью конструктора перемещения (int пришлось копировать). Но нужно ли перемещать или копировать адрес внутреннего элемента (от небольшого старого буфера до большого нового буфера). Это поведение согласуется с исходной частью вопроса (где также отображается внутренний элемент для изменения адресов).

И да, LWG 2321 участвует, хотя и не спорный момент. В моем ответе я уже предполагал LWG 2321. Нет никакого другого способа избежать событий, кроме чрезмерно нетерпеливых отладочных итераторов, чтобы безвозмездно (и неправильно) аннулировать себя. Итераторы без отладки никогда не будут аннулированы, и не будут указатели или ссылки.

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

Ответ 2

Это зависит от вашего конструктора перемещения A. но как он (*), он будет использовать конструктор перемещения vector<int> для A, и согласно http://www.cplusplus.com/reference/vector/vector/vector/

[..] никакие элементы не создаются (их собственность передается непосредственно).

Таким образом, указатели на сами int сохраняют свою силу.

Изменить: (*) A должен быть noexcept для этого, а std::vector не гарантируется noexcept.

Ответ 3

Элементы A будут перемещены там, где это возможно, и A имеет неявный конструктор перемещения и неявный оператор присваивания перемещения, поэтому вектор элемента также будет перемещен.

Теперь перемещение вектора необязательно эквивалентно a.swap(b), поэтому вы не можете полагаться на неявные функции перемещения, если хотите получить гарантию; вы можете написать свой собственный.

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

[C++11: 23.2.1/8]: Выражение a.swap(b) для контейнеров A и b стандартного типа контейнера, отличного от array, должно обмениваться значениями A и b без вызова какого-либо перемещения, копирования, или операции свопинга для отдельных элементов контейнера. [..]