Информация об изменениях

Сообщение Re[2]: Эффективный простреливатель ног C++ 11, 14, 17, 20 от 02.01.2021 18:43

Изменено 04.01.2021 20:33 watchmaker

Re[2]: Эффективный простреливатель ног C++ 11, 14, 17, 20
Здравствуйте, Nuzhny, Вы писали:

N>Здравствуйте, Тёмчик, Вы писали:


Тё>>С++11 emplace_back

Тё>>
Тё>>std::vector<int> v;
Тё>>v.emplace_back(123);
Тё>>v.emplace_back(v[0]);
Тё>>

Тё>>Думаете, результат будет [123, 123]? Think again.

N>Слушай, а что будет? Я проверил на Ideone, всё норм.


С++ гарантирует, что будет вектор из двух элементов [123, 123]. Релевантная ссылка: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526

Но это хороший тест, на котором ломаются многие самодельные аналоги std::vector.

В них относительно часто встречается баг наивной реализация вставки, устроенная примерно так:
1. Проверить size() < capacity(). Если выполнено перейти к пункту 3.
2. Позвать reserve(size() × 2)
3. Вызвать in-place конструктор для нового элемента по смещению size();
4. Сделать ++size;

где reserve работает так:
1. Выделяет новый буфер;
2. Переносит в него элементы из текущего буфера;
3. Разрушает элементы в старом буфере и освобождает память;



При аккуратном рассмотрении, видно, что если аргумент emplace_back (или push_back) ссылался на собственный элемент вектора и если при его вставке произошла реаллокация, то после пункта 2, элемент, который нужно было скопировать и вставить, окажется уже разрушенным. И вставлять станет нечего.
(и даже говорят, в некоторых древних версиях STL встречался этот баг)
Надёжный std::vector написать сложно :)


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


ЗЫ. Если интересно, часто эту проблему решают так: при реаллокации сначала выделяют новый буфер, создают в нём сразу новый элемент, и лишь затем переносят в новый буфер старые элементы. То есть в emplace_back нельзя использовать reserve непосредственно, и нужно элементы обрабатывать в особом порядке (сначала новый, потом старые, а не наоборот), что при первом прочтении выглядит немного странным.
Re[2]: Эффективный простреливатель ног C++ 11, 14, 17, 20
Здравствуйте, Nuzhny, Вы писали:

N>Здравствуйте, Тёмчик, Вы писали:


Тё>>С++11 emplace_back

Тё>>
Тё>>std::vector<int> v;
Тё>>v.emplace_back(123);
Тё>>v.emplace_back(v[0]);
Тё>>

Тё>>Думаете, результат будет [123, 123]? Think again.

N>Слушай, а что будет? Я проверил на Ideone, всё норм.


С++ гарантирует, что будет вектор из двух элементов [123, 123]. Релевантная ссылка: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526

Но это хороший тест, на котором ломаются многие самодельные аналоги std::vector.

В них относительно часто встречается наивная реализация вставки, устроенная примерно так:
1. Проверить size() < capacity(). Если выполнено перейти к пункту 3.
2. Позвать reserve(size() × 2)
3. Вызвать in-place конструктор для нового элемента по смещению size();
4. Сделать ++size;

где reserve работает так:
1. Выделяет новый буфер;
2. Переносит в него элементы из текущего буфера;
3. Разрушает элементы в старом буфере и освобождает память;



При аккуратном рассмотрении, видно, что если аргумент emplace_back (или push_back) ссылался на собственный элемент вектора и если при его вставке произошла реаллокация, то после пункта 2, элемент, который нужно было скопировать и вставить, окажется уже разрушенным. И вставлять станет нечего.
(и даже говорят, в некоторых древних версиях STL встречался этот баг)
Надёжный std::vector написать сложно :)


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


ЗЫ. Если интересно, часто эту проблему решают так: при реаллокации сначала выделяют новый буфер, создают в нём сразу новый элемент, и лишь затем переносят в новый буфер старые элементы. То есть в emplace_back нельзя использовать reserve непосредственно, и нужно элементы обрабатывать в особом порядке (сначала новый, потом старые, а не наоборот), что при первом прочтении выглядит немного странным.