std :: vector <std :: wstring> Допустимо ли перемещение / перераспределение внутреннего wstring.data ()? - PullRequest
3 голосов
/ 05 июля 2019

Вот выдержка:

...
std::vector<std::wstring> vecWstr;

vecWstr.emplace_back(L"1");
wchar_t* data1 = vecWstr[0].data(); //<-This pointer needed for future use.

vecWstr.emplace_back(L"2");
wchar_t* data2 = vecWstr[0].data();
if (data1 != data2)
   MessageBox(L"Error, not equal.", L"Compare");

MessageBox всегда возникает.
Итак, здесь я сравниваю два буфера wstring до и после .emplace(). В моем понимании они должны быть равны.

Основная проблема здесь заключается в следующем: почему vector перемещает / перераспределяет 1-й внутренний элемент std::wstring после включения второго?
Этот вопрос возник после того, как расследование произошло после странного поведения программы.
Если я сохраню vecWstr[0].data() указатель буфера до секунды .emplace(), указатель буфера устареет, и программа будет вести себя неадекватно.
Самая большая проблема заключается в том, что в программе много std::vector<std::wstring>, но все они, кажется, работают, как и ожидалось, и только один, как показано выше.
Это все в MSVS 16.1.5

Вопрос:
Кто здесь? Может ли std::vector изменять / перемещать внутренний буфер элементов std::wstring или нет?

1 Ответ

7 голосов
/ 05 июля 2019

В C ++ STL есть то, что называется недействительностью указателя. Это означает, что когда вы получаете указатель на элемент в контейнере, а затем модифицируете контейнер, после модификации ваш указатель может перестать действовать.

Правило аннулирования указателя определяется стандартом и варьируется от контейнеров к контейнерам, операций к операциям.

В вашем случае у вас есть std::vector. Ссылка / указатель / итератор на элемент вектора больше не действительны, если вы emplace_back и вектор требует большей емкости для добавленного элемента. В этом случае вектор выделяет еще большее пространство в памяти и перемещает туда все свои элементы.

Но подождите!

Вы берете указатель data() прямо из строки! Почему этот указатель также недействителен? Разве wstring не должен быть легкой структурой, которая просто содержит указатель на некоторый буфер кучи?

Ну, это магия SSO (Small String Optimization). Если ваша строка достаточно мала, wstring просто сохраняет свой буфер в самой структуре данных (а не хранит указатель на буфер). В этом случае, когда вы перемещаете его, конечно, указатель становится недействительным.

Ваша строка довольно мала (1 широкий символ), поэтому она удовлетворяет условию SSO. Если вы используете более длинные:

std::vector<std::wstring> vecWstr;

vecWstr.emplace_back(L"asdfghjkl");
wchar_t* data1 = vecWstr[0].data(); //<-This pointer needed for future use.

vecWstr.emplace_back(L"qwertyuiop");
wchar_t* data2 = vecWstr[0].data();
if (data1 != data2)
    MessageBox(0, L"Error, not equal.", L"Compare", 0);
return 0;

Окно сообщения, вероятно, не появится.

Однако вы не можете контролировать длину строки времени выполнения, и вы не знаете, как ваш компилятор будет реализовывать SSO, поэтому не кодируйте этот путь!

Вместо этого вы можете использовать метод reserve (как предложил songyuanyao) или использовать другие контейнеры, которые не делают недействительными указатели при добавлении элемента. Пожалуйста, обратитесь к std :: list и std :: deque . Прочитайте разделы об их недействительности указателя / ссылки / итератора.


...