Использование итератора std::vector после вызова clear()

Эта проблема очень странная, у меня есть следующий код:

#include <iostream>
#include <thread>
#include <chrono>
#include <string>
#include <vector>

struct Data {
    Data(const char * d)
        :data(d){}
    std::string data;
};

// Using these data to avoid SOO(aka small object optimize) ASAP
std::vector<Data> arr {"11111111111111111111111111111111111111111111111111111111",
                        "2222222222222222222222222222222222222222222222222222222",
                        "3333333333333333333333333333333333333333333333333333333",
                        "4444444444444444444444444444444444444444444444444444444",
                        "5555555555555555555555555555555555555555555555555555555"};

class Test {
public:
  void test() {
    // I get a valid iterator first
    std::vector<Data>::iterator it = arr.begin();

    // I run a thread to clear arr
    std::thread([](){
          std::this_thread::sleep_for(std::chrono::seconds(2));
          arr.clear();
        }).detach();
    // sleep 3s to make following code run after thread
    std::this_thread::sleep_for(std::chrono::seconds(3));
    // I think it->data is a invalid operation, Howerver, it works :(
    std::string res = it->data;
  }
};

int main() {
  Test test;
  test.test();
  std::this_thread::sleep_for(std::chrono::seconds(5));
  return 0;
}

Этот случай показывает посещение итератора после вызова clear(), теоретически это недопустимая операция, но она работает, мне очень интересно, почему, почему, почему???

Неопределенное поведение не определено. То, что вектор был очищен, не означает, что его память была освобождена. Даже если память была освобождена, данные все еще там, пока что-то еще не появится и не использует эту память.

Miles Budnek 03.04.2023 02:41

@MilesBudnek, поскольку вектор никогда не объявлялся изменчивым, компилятор может даже использовать его части, которые были кэшированы до clear! Вы действительно не можете ни на что полагаться, когда возникает неопределенное поведение.

Mark Ransom 03.04.2023 03:44

После вызова clear() все итераторы недействительны. См. vector::clear(). Их использование является неопределенным поведением.

Martin York 03.04.2023 06:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

использование clear() стирает все элементы и заставляет все последующие вызовы size() возвращать ноль

однако это не меняет пропускную способность вектора

Обратите внимание, что поведение undefined может привести к странным вещам.

clear() вызывает деструкторы содержимого вектора. Они необходимы только для освобождения любой динамической памяти, которой владеют объекты. Компиляторы/библиотеки отличаются чем-то еще. MSVC имеет тенденцию изменять строковые объекты, чтобы они имели нулевую длину. Вероятно, чтобы предотвратить сбой плохо написанного кода за счет ненужного генерирования кода. UB в любом случае, если используется.
doug 03.04.2023 03:31

Ответ неполный. Обратите внимание, что вы предполагаете, что код, создаваемый компилятором, является нормальным и, похоже, предполагает, что сон синхронизирует данные. Но на самом деле даже компилятору будет позволено дублировать этот вектор, потому что это UB. Кроме того, тот факт, что вектор очищен, без какого-либо ограждения или синхронизации не обязательно должен быть виден из других потоков. Возможно даже, что итератор, память и блок управления остались нетронутыми.

Fabian Keßler 03.04.2023 03:37

@doug все stls должны изменять содержимое строки, потому что строка определена для хранения строки c (с нулевым завершением). Поэтому по крайней мере первый символ должен быть заменен нулем.

Fabian Keßler 03.04.2023 03:41

@FabianKeßler, когда объект удален, нет абсолютно никаких ограничений на его прежнее содержимое. Сами струны никто не чистил.

Mark Ransom 03.04.2023 03:56

@FabianKeßler Не так. См. этот Clang ничего не меняет, когда строковый объект уничтожается. MSVC, OTOH, изменяет его на то же, что и std::move. Пустая трата кода для dtor. Код примера содержит технический UB, но соответствует дампу памяти.

doug 03.04.2023 05:14

@doug Я не должен отвечать на что-то, когда я устал. Почему-то я подумал, что вы имели в виду, что содержимое строк не изменяется после вызова clear в строке.

Fabian Keßler 03.04.2023 09:59

Другие вопросы по теме