Страницы

Поиск по вопросам

вторник, 16 октября 2018 г.

Обеспечение строгой гарантии безопасности

Прохожу курс по C++ и пытаюсь ответить на такой вопрос:
Если у класса, объекты которого хранятся в стеке, есть небросающие перемещающие методы, то можно реализовать такой возвращающий pop со строгой гарантией безопасности исключений (отметьте все верные утверждения)
Обеспечить строгую гарантию безопасности для метода pop, который возвращает выталкиваемый элемент, не получается из-за возможного исключения при копировании возвращаемого объекта. Если у класса, объекты которого хранятся в стеке, есть перемещающий конструктор, то исходная реализация (см. степ 10) обеспечивает строгую гарантию безопасности исключений. Если у класса, объекты которого хранятся в стеке, есть небросающий перемещающий конструктор, то исходная реализация (см. степ 10) обеспечивает строгую гарантию безопасности исключений. Если у класса, объекты которого хранятся в стеке, есть небросающий перемещающий конструктор, то исходная реализация (см. степ 10) не обеспечивает строгую гарантию безопасности исключений, но этого можно добиться добавив std::move в строке с return. Проблема обеспечения строгой гарантии безопасности возвращающего метода pop возникает только для стеков, которые реализованы на массиве. Проблема обеспечения строгой гарантии безопасности возвращающего метода pop актуальна для произвольных контейнеров.
Исходная реализация вот такая:
template struct Stack { void Push(const T& t) { data_.push_back(t) }
T pop() { T tmp = data_.back(); data_.pop_back(); return tmp; }
std::vector data_; };
Как мне кажется, если в return добавить std::move, то мы явно вызовем перемещающий метод и все будет хорошо. Ну и проблема обеспечения СГБ вполне актуальна не только для стеков на массиве, не вижу тут совсем никакой связи с массивом, т.к. проблема в гарантии возникает из за возможной ошибки при копировании, потому что объект возвращается по значению. Поэтому отвечаю 1, 4 и 6, но это неверно. Подскажите пожалуйста где ошибка в моем мышлении?
UPD
Правильно: 1, 3, 6. Объяснение см. в комментариях к правильному ответу на вопрос ну и сам правильный ответ :)


Ответ

Как мне кажется, если в return добавить std::move, то мы явно вызовем перемещающий метод и все будет хорошо.
Так как после изменения контейнера у нас идет только return, то необходимо, чтобы этот участок был безопасен относительно исключений, всё что было до изменения контейнера нам не важно. Также примем за правду тот факт, что деструктор T не выбрасывает исключений, в ином случае обеспечить строгую гарантию нельзя. Поэтому для начала разберемся с копированием возвращаемого значения. Компилятор постарается применить оптимизации RVO/NRVO, чтобы избежать как копирования, так и перемещения возвращаемого значения. В случае, если такая оптимизация не удастся, то в return по-возможности будет перемещение tmp, а не его копирование, согласно стандарту:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration , and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

Обеспечить строгую гарантию безопасности для метода pop, который возвращает выталкиваемый элемент, не получается из-за возможного исключения при копировании возвращаемого объекта.
Объект по-возможности будет перемещен, так что этот пункт отпадает, ведь у нас явно задано в условии, что объект может перемещаться не выбрасывая исключений. Если же объект может выбросить исключение при перемещении, или если перемещение невозможно и при копировании может вылететь исключение, то строгую безопасность такой pop не обеспечивает.
Если у класса, объекты которого хранятся в стеке, есть перемещающий конструктор, то исходная реализация (см. степ 10) обеспечивает строгую гарантию безопасности исключений.
Нет, ведь перемещающий конструктор может бросить исключение, а объект из стека уже удален.
Если у класса, объекты которого хранятся в стеке, есть небросающий перемещающий конструктор, то исходная реализация (см. степ 10) обеспечивает строгую гарантию безопасности исключений.
Это верно.
Если у класса, объекты которого хранятся в стеке, есть небросающий перемещающий конструктор, то исходная реализация (см. степ 10) не обеспечивает строгую гарантию безопасности исключений, но этого можно добиться добавив std::move в строке с return.
Нет, исходная реализация уже обеспечивает такую гарантию. Как сказано выше, сначала компилятор захочет вообще убрать, и копирование, и перемещение. Если не удастся, то tmp будет рассматриваться как rvalue. А вот если применить std::move, то это может поломать компилятору возможность сделать RVO/NRVO, т.е. лучше оно не сделает, но может сделать хуже в плане оптимизаций.
Проблема обеспечения строгой гарантии безопасности возвращающего метода pop возникает только для стеков, которые реализованы на массиве.
Имеется ввиду просто массив вида T[N] или любой контейнер, представляющий структуру данных массив? В первом случае удаление из массива одного элемента невозможно, разве что вручную управлять временем жизни объектов, но тогда массив должен быть не T[N], а, например, char[N * sizeof(T)] с соответствующим выравниванием. При таком подходе вся безопасность ложиться на обертку, которая управляет этим массивом. Во втором же, всё должно быть в порядке, если сам pop_back не бросает исключений. Поэтому в общем случае, с массивом будет всё нормально.
Проблема обеспечения строгой гарантии безопасности возвращающего метода pop актуальна для произвольных контейнеров.
Разве что произвольный контейнер сам по себе не обеспечивает эту безопасность при удалении последнего элемента, поэтому всё будет зависеть от реализации контейнера.
Возможно, я в чем-то не прав и меня поправят.

Комментариев нет:

Отправить комментарий