Ранее мы с вами рассматривали ситуации, где в конструкторе или функции reserve в нашем векторе приходилось обрабатывать возможные исключения. Такие исключения могут покинуть пределы этих функций. В конструкторе тогда считается, что объект не создан, а если такое исключение произойдет в функции reserve или функции resize, то тогда вектор у нас уже существовал, он должен остаться в каком-то состоянии. Какие гарантии будет предоставлять наш класс вектор в этих случаях? Когда рассматриваются такие вопросы, то обычно выделяют следующие виды гарантий. Про каждую функцию обычно можно сказать, какая она: либо она дает гарантию отсутствия исключений, когда гарантируется, что никакие исключения из этой функции вообще не вылетают (не про каждую функцию такое можно утверждать), некоторые функции могут предоставлять строгую гарантию. Строгая гарантия означает, что эта функция в векторе работает как транзакция. Если в процессе работы случится какой-то сбой, то старое состояние вектора останется неизменным. Наконец, некоторые функции могут гарантировать лишь базовую гарантию. Это означает, что в случае сбоя вектор будет в согласованном, но не обязательно в точно таком же состоянии, в котором он был до вызова этой функции. Что значит согласованное состояние? Для нашего вектора это означает, что, например, его поля "size" или "capacity" являются актуальными и действительно отражают то состояние сырой памяти, которое хранится в этом векторе, что, например, сайт случайно не увеличен на единицу, хотя элементов там осталось столько же, сколько было. Давайте сейчас попробуем пройтись по функциям нашего вектора и понять, какие гарантии они предоставляют. Для функций, которые вообще гарантированно не генерируют исключений, компилятор предоставляет отдельный специальный тег под названием "noexcept", с помощью которого такие функции можно пометить. Такая помета нужна не только для того, чтобы выразить свою мысль о тех гарантиях, которые предоставляет такая функция, но и для того, чтобы компилятор, зная об этом, мог сгенерировать более эффективный код и не предполагал внутри таких функций раскрутку стека. В первую очередь давайте пометим таким тегом функцию swap. Здесь у нас в структуре RawMemory меняются местами указатель и число, это точно не приводит к исключениям, поэтому сбоев здесь не бывает. Конструктор по умолчанию, поскольку мы тут написали "default", он автоматически будет с тегом "noexcept". Деструктор: мы тоже могли бы его пометить тегом "noexcept", но это тоже происходит автоматически. Не будем здесь это писать. Предполагается, что исключения не покидают пределов деструкторов нашего класса. А вот move конструктор: он тоже бессбойный, помечаем его таким же тегом. Заметим, что обычно конструктор, который аллоцирует какую-то память, конечно же, может обернуться неудачей, если этой памяти банально не хватило. Давайте пойдем дальше. Не будем пока говорить про вот эти операторы, это нам не очень интересно. Мы посмотрим на функции вектора и точно так же пометим тегом "noexcept" функцию swap. Вот эти конструкторы исключения могут генерировать, поэтому про них мы поговорим позже. Пометим тегом "noexcept" move конструктор, деструктор помечен автоматически, писать тут необязательно, и пометим таким тегом оператор move присваивания, потому что он просто вызывает swap. Есть ли еще какие-то функции, про которые заведомо можно сказать, что исключения не покидают их пределов? Разве что функция pop_back, но стандарт предполагает, что в каком-нибудь режиме отладочной сборки у вас может быть ситуация, когда генерируется исключение, если вектор пустой, поэтому если вы посмотрите на стандартный вектор, то такие функции там таким тегом не помечены, хотя в случае, если вы его вызываете корректно, она тоже гарантирует отсутствие исключений. Давайте сейчас посмотрим на другое семейство функций: reserve, resize, push_back и так далее. Какие гарантии предоставляют они? Что если внутри функции reserve произойдет исключение? Это может произойти в следующих местах: во-первых, в конструкторе вот этой переменной data2, если просто не хватило памяти. В этом случае просто локальная переменная не создана, старый вектор мы никак не трогали, и поэтому в данном случае эта функция гарантирует, что вектор останется нетронутым в таком же виде, как и был. Может ли произойти исключение вот здесь, внутри функции uninitialized_move? Давайте вспомним, что эта функция предпочитает перемещать элементы, если у класса t имеется конструктор перемещения, и мы верим, что в этом случае он не генерирует исключений (давайте это предполагать), а также эта функция будет просто вызывать конструктор копирования, если никакого оператора перемещения нет. Мы в случае копирования можем тоже получить исключение, это будет копирование в какой-то новый блок сырой памяти data2, но в этом случае в старом векторе у нас ничего не испортится. У нас могло бы испортиться, если мы оттуда переместили элемент, а потом получили бы исключение, но конструктор перемещения, как я уже сказал, по нашему предположению, не приводит ко сбою. Мы здесь заменяем data2 на data в самый последний момент, когда мы уверены, что мы дошли до этой строчки, и никакого сбоя ранее не случилось. Вплоть до этого момента, если сбой произошел, то вектор остается в исходном старом состоянии. Поэтому мы говорим, что эта функция дает строгую гарантию безопасности. Давайте посмотрим на функцию resize. Она вызывает reserve, которая, как мы уже сказали, дает строгую гарантию, а дальше пытается сконструировать элементы, и мы помним, что такие функции uninitialized_value_construct — это функции транзакции. Если там внутри при копировании элемента или при создании элемента произойдет сбой, то цикл откатит и удалит все элементы, которые были созданы до этого. Поэтому максимум, что может произойти с таким вектором в случае сбоя, это то, что мы случайно зачем-то переаллоцировали его в другое место, но породить там новые элементы не смогли. В этом случае все элементы остались такими же, как были, просто они живут теперь в новых адресах. Давайте считать, что это тоже случай строгой гарантии. В наших предположениях эта функция дает строгую гарантию безопасности. Обратите внимание, как легко можно было бы написать эту функцию неправильно. Я не случайно в этой функции присваиваю этот размер в самом конце. Если бы мы вдруг, извините, переместили бы вот эту строчку в начало, куда-нибудь вот сюда, мы легко бы могли получить программу, которая возвращает нам несогласованный вектор в случае сбоя. Тогда у нас не было бы даже базовой гарантии. Представьте себе, что вот здесь, внутри uninitialized_value_construct произошло исключение, мы аварийно покидаем программу, но переменная z увеличилась, хотя элементы реально в векторе не созданы. Поэтому вот такие переменные надо менять в самом конце, когда мы уверены, что мы до этого места дошли. Несложно догадаться, какую гарантию безопасности предоставляет функция push_back — это тоже строгая гарантия. Какая же функция предоставляет лишь базовую гарантию безопасности? Оказывается, что это написанный нами оператор присваивания, и давайте разберемся, почему. Если бы мы его писали с помощью идиомы copy-and-swap, тут тоже была бы строгая гарантия. Однако, мы хотим сэкономить и не делать лишнюю реаллокацию, если она нам не нужна, если нам и старой памяти хватает. Однако, вот здесь в операторах присваивания для элемента типа t или в копировании тоже может произойти сбой. Представьте, что он произошел внутри вот этого оператора if во время копирования. Мы, конечно, новые элементы не добавим, но старые элементы у нас уже, вообще говоря, перезаписались. Это не такие же элементы, которые были в векторе до этого. Поэтому здесь надо делать выбор между тем, чтобы оператор присваивания работал всегда со строгой гарантией, работал как настоящая транзакция, и тем, чтобы он работал эффективно. Разработчики стандартной библиотеки выбрали второе. Он работает эффективно, но зато не всегда гарантирует, что в случае исключения вектор останется ровно таким же, как и был, но он заведомо останется согласованным. Его размер и его capacity будут отражать реальное состояние дел. Итак, здесь у нас только базовая гарантия. В этом видео мы поговорили про те гарантии безопасности и исключений, которые предоставляют функции нашего вектора.