Можно ли сделать exception-safe T Stack::pop()? Например, так:
template <class T> class Stack {
T Pop();
private:
T* v_; // ptr to a memory area big
size_t vsize_; // enough for 'vsize_' T's
size_t vused_; // # of T's actually in use
};
template<class T>
T Stack<T>::Pop()
{
if( vused_ == 0)
throw"pop from empty stack";
T result = v_[vused_-1];
--vused_;
try {
return result;
} catch (...) {
++vused_;
throw;
}
}
Здравствуйте, Marty, Вы писали:
M>Здравствуйте, T4r4sB, Вы писали:
NI>>>std::move_if_noexcept
TB>>Что это за срань? На С++ чтоб писать, это всё надо знать?
M>Ну, можешь писать на C с классами
А можно писать на С с шаблонами и деструкторами? Но без std::one_more_shitty_builtin?
TB>>>Что это за срань? На С++ чтоб писать, это всё надо знать?
M>>Ну, можешь писать на C с классами
TB>А можно писать на С с шаблонами и деструкторами? Но без std::one_more_shitty_builtin?
Можно. Пиши
ЗЫ На самом деле например эти всякие штуки из type_traits, которые появились в 11 стандарте, очень удобная штука — делать специализации для разных типов — интегральное/нет, целое/нет, ссылка/указатель/значений, и тп
Еще понравилось std::make_unsigned — параметр шаблона задавал размерность в битах, но могли ведь подсунуть и знаковый, что всё поломало бы. А так — из заданного типа вывел его unsigned аналог и всё.
Многое пока правда не понятно, многого не знаю, но в целом — имхо полезного много появилось
Здравствуйте, N. I., Вы писали:
NI>Максим Рогожин:
МР>>Можно ли сделать exception-safe T Stack::pop()?
NI>Можно.
А что тогда в статьях про Exception Safety пишут, что T Stack::pop() не может быть полностью exception safe и поэтому его разбили на два метода T top() и void pop()?
Максим Рогожин:
МР>А что тогда в статьях про Exception Safety пишут, что T Stack::pop() не может быть полностью exception safe
Когда эти статьи писались, получение результата и удаление элемента одной транзакцией именно в таком виде было нереализуемо, хотя в случае простых типов такое было возможно и в C++98.
МР>поэтому его разбили на два метода T top() и void pop()
Версия pop, которая просто удаляет элемент, всё равно нужна, чтоб была возможность гарантированного удаления без выброса исключений и без оверхеда от копирования — на случай, если значение удаляемого элемента не интересует.
Здравствуйте, T4r4sB, Вы писали:
NI>>std::move_if_noexcept
TB>Что это за срань? На С++ чтоб писать, это всё надо знать?
Все знать невозможно, коночно. Но можно попобовать добавить позитива в свои оценки. Я вот в последнее время прежде, чем мастерить что-нибудь на коленке, сначала смотрю, а нет ли этого уже в стандартной библиотеке. И нередко бывает: "Ух ты, а такая срань есть уже!". И так приятно сразу
--
Не можешь достичь желаемого — пожелай достигнутого.
NI>Когда эти статьи писались, получение результата и удаление элемента одной транзакцией именно в таком виде было нереализуемо, хотя в случае простых типов такое было возможно и в C++98.
что изменилось с тех пор? почему такое стало возможным?
зы. давай механизм move оставим за скобками, т.к. он лишь замыливает взгляд и сфокусируемся на типах T, которые копируются, но не муваются
uzhas:
U>что изменилось с тех пор? почему такое стало возможным?
В общем, оно и раньше было возможным при условии, что компилятор гарантированно делает copy elision в нужных местах: заводишь локальную переменную, копируешь в неё результат, вызываешь деструктор у топового элемента, уменьшаешь счётчик числа элементов на единицу — всего-то и делов. Если компилятор применяет NRVO, то возвращённый объект можно поюзать, не боясь его "потерять" из-за исключения при копировании.
Если же брать худший сценарий, когда NRVO нет, то деструктор придётся вызывать после инициализации return object, и всунуть такой вызов можно разве что в другой деструктор, который должен как-то определить, было ли брошено исключение. В C++17 наличие нового исключения можно обнаружить с помощью std::uncaught_exceptions, а в более ранних плюсах такой функции нету.
Здравствуйте, Marty, Вы писали:
M>ЗЫ На самом деле например эти всякие штуки из type_traits, которые появились в 11 стандарте, очень удобная штука — делать специализации для разных типов — интегральное/нет, целое/нет, ссылка/указатель/значений, и тп M>Многое пока правда не понятно, многого не знаю, но в целом — имхо полезного много появилось
Они ведь и появились что бы героически бороться с проблемами которые вызваны собственно самим C++
uzhas:
U>в общем, ничего не изменилось с тех пор и статья актуальна и по сей день
"Актуальность" там разве что теоретически-познавательная, т.к. чтобы реально наткнуться на описанные проблемы, надо ещё хорошенько постараться в поисках компилятора без достаточной поддержки copy elision или специфических ключей, которые copy elision отключают.
U>действительно считаешь, что именно эта функция сделал прорыв и позволила сделать безопасным T pop() ?
Ну, попробуй найти сценарий, при котором
auto top_value = stack.pop();
удалит из stack топовый элемент без успешной инициализации top_value.
Здравствуйте, N. I., Вы писали:
NI>Если же брать худший сценарий, когда NRVO нет, то деструктор придётся вызывать после инициализации return object, и всунуть такой вызов можно разве что в другой деструктор, который должен как-то определить, было ли брошено исключение. В C++17 наличие нового исключения можно обнаружить с помощью std::uncaught_exceptions, а в более ранних плюсах такой функции нету.
Не, ну если уж погружаться в паранойю, то можно возвращать из pop() умный указатель, например так: typedef std::vector<std::shared_ptr<T>> stack_body
Только это всё в целом бессмысленно. Сама по себе концепция exception-safe довольно бессмысленная...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Максим Рогожин:
МР>А тот код, который я привел (обернуть return в try-catch) не решает проблему?
Если push и pop делать канонично — с инициализацией (а не присваиванием) и уничтожением элемента соответственно, — то одними try и catch там не обойдёшься.
Кроме того, до C++11 было дововольно мало "безопасных" способов поюзать результат такого pop при бросающем конструкторе копирования (если рассматривать некий сферический компилятор в вакууме без каких-либо гарантий, помимо описанных в стандарте C++). Например, тут
T t = stack.pop();
результат можно было "потерять" из-за исключения при конструировании t. А вот тут
T const &t = stack.pop();
— из-за исключения при конструировании временного объекта, который компилятору позволялось создавать в контексте инициализации ссылки t (интересно, когда-либо вообще были такие компиляторы, которые этим правом пользовались?).
Оставалось разве что метод какой-нибудь вызвать
stack.pop().member_function();
или к отдельному полю доступ получить
variable = stack.pop().data_member;
Не шибко богатый выбор.
В C++11 правила инициализации ссылок уже более вменяемые — там можно гарантированно забайндить ссылку непосредственно на return object функции pop
auto &&t = stack.pop();
и дальше делать с этим объектом что хочешь.
C++17 в добавок к этому гарантирует, что в следующем случае
auto t = stack.pop();
переменная t и return object функции pop являются одним и тем же объектом.
Здравствуйте, N. I., Вы писали:
NI>Кроме того, до C++11 было дововольно мало "безопасных" способов поюзать результат такого pop при бросающем конструкторе копирования (если рассматривать некий сферический компилятор в вакууме без каких-либо гарантий, помимо описанных в стандарте C++).
ты хочешь сказать, что из-за того, что в свежем стандарте появилось больше безопасных вариантов вызова метода T pop() проблема как бы исчезла?
она не исчезла, т.к. "опасные" варианты всё еще остались. в стандарте нет таких примеров, что вот вызывайте std::vector::at (для примера) только в одну строчку с присваиванием, иначе мы не даем гарантии на безопасность к исключениям. это ерунда какая-то, слишком уж много нюансов надо держать в голове