Exception safe T Stack::pop() ?
От: Максим Рогожин Россия  
Дата: 19.08.18 18:47
Оценка:
Привет!

Можно ли сделать 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;
  }
}
Отредактировано 19.08.2018 18:50 Максим Рогожин . Предыдущая версия .
Re: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 19.08.18 20:39
Оценка: 6 (1) +1
Максим Рогожин:

МР>Можно ли сделать exception-safe T Stack::pop()?


Можно.

МР>Например, так:


Так будет пооптимальнее:

template<class T>
    T Stack<T>::Pop() noexcept(std::is_nothrow_move_constructible_v<T>)
{
    if (vused_ == 0)
        internal_program_error("pop from empty stack");

    try
    {
        return std::move_if_noexcept(v_[--vused_]);
    }
    catch (...)
    {
        ++vused_;
        throw;
    }
}

С вызовом деструктора будет примерно так:

template <class F>
    class scope_exit_wrapper
{
public:
    scope_exit_wrapper(F &&f) :
        m_f(std::forward<F>(f)) {}
    ~scope_exit_wrapper()
    {
        m_f();
    }
    scope_exit_wrapper(scope_exit_wrapper const &) = delete;
private:
    F m_f;
};

template <class F>
    class scope_success_wrapper
{
public:
    scope_success_wrapper(F &&f) :
        m_f(std::forward<F>(f)),
        m_uncaught_exceptions(std::uncaught_exceptions())
    {}
    ~scope_success_wrapper() noexcept(false)
    {
        if (std::uncaught_exceptions() == m_uncaught_exceptions)
            m_f();
    }
    scope_success_wrapper(scope_success_wrapper const &) = delete;
private:
    F m_f;
    int m_uncaught_exceptions;
};

struct scope_exit_wrapper_arg_t {};
struct scope_success_wrapper_arg_t {};

template <class F>
    scope_exit_wrapper<F> operator <<(scope_exit_wrapper_arg_t, F &&f)
        { return {std::forward<F>(f)}; }
template <class F>
    scope_success_wrapper<F> operator <<(scope_success_wrapper_arg_t, F &&f)
        { return {std::forward<F>(f)}; }

#define PP_CONCAT_IMPL(x1, x2) x1##x2
#define PP_CONCAT(x1, x2) PP_CONCAT_IMPL(x1, x2)

#define SCOPE_EXIT(...)                                               \
    [[maybe_unused]] auto &&PP_CONCAT(SCOPE_EXIT_VAR_, __LINE__) =    \
        scope_exit_wrapper_arg_t() << [__VA_ARGS__]() noexcept -> void

#define SCOPE_SUCCESS(...)                                            \
    [[maybe_unused]] auto &&PP_CONCAT(SCOPE_SUCCESS_VAR_, __LINE__) = \
        scope_success_wrapper_arg_t() << [__VA_ARGS__]() -> void

template<class T>
    T Stack<T>::Pop() noexcept(std::is_nothrow_move_constructible_v<T>)
{
    if (vused_ == 0)
        internal_program_error("pop from empty stack");

    if constexpr (std::is_nothrow_move_constructible_v<T>)
    {
        SCOPE_EXIT(&){ v_[vused_].~T(); };
        return std::move(v_[--vused_]);
    }
    else
    {
        SCOPE_SUCCESS(&){ v_[--vused_].~T(); };
        return v_[vused_ - 1];
    }
}
Отредактировано 20.08.2018 13:31 N. I. . Предыдущая версия . Еще …
Отредактировано 20.08.2018 5:45 N. I. . Предыдущая версия .
Отредактировано 19.08.2018 21:21 N. I. . Предыдущая версия .
Re[2]: Exception safe T Stack::pop() ?
От: T4r4sB Россия  
Дата: 19.08.18 21:40
Оценка: :)
Здравствуйте, N. I., Вы писали:

NI>std::move_if_noexcept


Что это за срань? На С++ чтоб писать, это всё надо знать?
Re[3]: Exception safe T Stack::pop() ?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 19.08.18 21:56
Оценка: +1
Здравствуйте, T4r4sB, Вы писали:

NI>>std::move_if_noexcept


TB>Что это за срань? На С++ чтоб писать, это всё надо знать?


Ну, можешь писать на C с классами
Маньяк Робокряк колесит по городу
Re[4]: Exception safe T Stack::pop() ?
От: T4r4sB Россия  
Дата: 19.08.18 22:04
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте, T4r4sB, Вы писали:


NI>>>std::move_if_noexcept


TB>>Что это за срань? На С++ чтоб писать, это всё надо знать?


M>Ну, можешь писать на C с классами


А можно писать на С с шаблонами и деструкторами? Но без std::one_more_shitty_builtin?
Re[5]: Exception safe T Stack::pop() ?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 19.08.18 22:40
Оценка: +1
Здравствуйте, T4r4sB, Вы писали:


TB>>>Что это за срань? На С++ чтоб писать, это всё надо знать?


M>>Ну, можешь писать на C с классами


TB>А можно писать на С с шаблонами и деструкторами? Но без std::one_more_shitty_builtin?


Можно. Пиши


ЗЫ На самом деле например эти всякие штуки из type_traits, которые появились в 11 стандарте, очень удобная штука — делать специализации для разных типов — интегральное/нет, целое/нет, ссылка/указатель/значений, и тп

Еще понравилось std::make_unsigned — параметр шаблона задавал размерность в битах, но могли ведь подсунуть и знаковый, что всё поломало бы. А так — из заданного типа вывел его unsigned аналог и всё.

Многое пока правда не понятно, многого не знаю, но в целом — имхо полезного много появилось
Маньяк Робокряк колесит по городу
Re[2]: Exception safe T Stack::pop() ?
От: Максим Рогожин Россия  
Дата: 20.08.18 03:08
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Максим Рогожин:


МР>>Можно ли сделать exception-safe T Stack::pop()?


NI>Можно.


А что тогда в статьях про Exception Safety пишут, что T Stack::pop() не может быть полностью exception safe и поэтому его разбили на два метода T top() и void pop()?
Re[3]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 20.08.18 06:15
Оценка: +1
Максим Рогожин:

МР>А что тогда в статьях про Exception Safety пишут, что T Stack::pop() не может быть полностью exception safe


Когда эти статьи писались, получение результата и удаление элемента одной транзакцией именно в таком виде было нереализуемо, хотя в случае простых типов такое было возможно и в C++98.

МР>поэтому его разбили на два метода T top() и void pop()


Версия pop, которая просто удаляет элемент, всё равно нужна, чтоб была возможность гарантированного удаления без выброса исключений и без оверхеда от копирования — на случай, если значение удаляемого элемента не интересует.
Re[3]: Exception safe T Stack::pop() ?
От: rg45 СССР  
Дата: 20.08.18 07:18
Оценка: :)
Здравствуйте, T4r4sB, Вы писали:

NI>>std::move_if_noexcept


TB>Что это за срань? На С++ чтоб писать, это всё надо знать?


Все знать невозможно, коночно. Но можно попобовать добавить позитива в свои оценки. Я вот в последнее время прежде, чем мастерить что-нибудь на коленке, сначала смотрю, а нет ли этого уже в стандартной библиотеке. И нередко бывает: "Ух ты, а такая срань есть уже!". И так приятно сразу
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Exception safe T Stack::pop() ?
От: uzhas Ниоткуда  
Дата: 20.08.18 10:10
Оценка:
Здравствуйте, N. I., Вы писали:


NI>Когда эти статьи писались, получение результата и удаление элемента одной транзакцией именно в таком виде было нереализуемо, хотя в случае простых типов такое было возможно и в C++98.


что изменилось с тех пор? почему такое стало возможным?
зы. давай механизм move оставим за скобками, т.к. он лишь замыливает взгляд и сфокусируемся на типах T, которые копируются, но не муваются
Re[5]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 20.08.18 11:28
Оценка:
uzhas:

U>что изменилось с тех пор? почему такое стало возможным?


В общем, оно и раньше было возможным при условии, что компилятор гарантированно делает copy elision в нужных местах: заводишь локальную переменную, копируешь в неё результат, вызываешь деструктор у топового элемента, уменьшаешь счётчик числа элементов на единицу — всего-то и делов. Если компилятор применяет NRVO, то возвращённый объект можно поюзать, не боясь его "потерять" из-за исключения при копировании.

Если же брать худший сценарий, когда NRVO нет, то деструктор придётся вызывать после инициализации return object, и всунуть такой вызов можно разве что в другой деструктор, который должен как-то определить, было ли брошено исключение. В C++17 наличие нового исключения можно обнаружить с помощью std::uncaught_exceptions, а в более ранних плюсах такой функции нету.
Re[6]: Exception safe T Stack::pop() ?
От: kov_serg Россия  
Дата: 20.08.18 11:35
Оценка: +1
Здравствуйте, Marty, Вы писали:

M>ЗЫ На самом деле например эти всякие штуки из type_traits, которые появились в 11 стандарте, очень удобная штука — делать специализации для разных типов — интегральное/нет, целое/нет, ссылка/указатель/значений, и тп

M>Многое пока правда не понятно, многого не знаю, но в целом — имхо полезного много появилось

Они ведь и появились что бы героически бороться с проблемами которые вызваны собственно самим C++
Re[6]: Exception safe T Stack::pop() ?
От: uzhas Ниоткуда  
Дата: 20.08.18 11:49
Оценка:
Здравствуйте, N. I., Вы писали:

NI>В общем


в общем, ничего не изменилось с тех пор и статья актуальна и по сей день
ах вот, что изменилось:

В C++17 наличие нового исключения можно обнаружить с помощью std::uncaught_exceptions, а в более ранних плюсах такой функции нету.


действительно считаешь, что именно эта функция сделал прорыв и позволила сделать безопасным T pop() ?
Отредактировано 20.08.2018 11:55 uzhas . Предыдущая версия .
Re[7]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 20.08.18 12:42
Оценка:
uzhas:

U>в общем, ничего не изменилось с тех пор и статья актуальна и по сей день


"Актуальность" там разве что теоретически-познавательная, т.к. чтобы реально наткнуться на описанные проблемы, надо ещё хорошенько постараться в поисках компилятора без достаточной поддержки copy elision или специфических ключей, которые copy elision отключают.

U>действительно считаешь, что именно эта функция сделал прорыв и позволила сделать безопасным T pop() ?


Ну, попробуй найти сценарий, при котором

auto top_value = stack.pop();

удалит из stack топовый элемент без успешной инициализации top_value.
Re[2]: Exception safe T Stack::pop() ?
От: σ  
Дата: 20.08.18 13:07
Оценка:
NI>
#define SCOPE_EXIT(...)                                               \
    [[maybe_unused]] auto &&PP_CONCAT(SCOPE_SUCCESS_VAR_, __LINE__) = \


#define SCOPE_EXIT(...)                                               \
    [[maybe_unused]] auto &&PP_CONCAT(SCOPE_EXIT_VAR_, __LINE__) = \
Re[6]: Exception safe T Stack::pop() ?
От: Erop Россия  
Дата: 20.08.18 14:06
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Если же брать худший сценарий, когда NRVO нет, то деструктор придётся вызывать после инициализации return object, и всунуть такой вызов можно разве что в другой деструктор, который должен как-то определить, было ли брошено исключение. В C++17 наличие нового исключения можно обнаружить с помощью std::uncaught_exceptions, а в более ранних плюсах такой функции нету.


Не, ну если уж погружаться в паранойю, то можно возвращать из pop() умный указатель, например так: typedef std::vector<std::shared_ptr<T>> stack_body

Только это всё в целом бессмысленно. Сама по себе концепция exception-safe довольно бессмысленная...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: Exception safe T Stack::pop() ?
От: Максим Рогожин Россия  
Дата: 20.08.18 15:48
Оценка:
Здравствуйте, uzhas, Вы писали:

U>в общем, ничего не изменилось с тех пор и статья актуальна и по сей день


А тот код, который я привел (обернуть return в try-catch) не решает проблему?
Re[7]: Exception safe T Stack::pop() ?
От: andyp  
Дата: 20.08.18 16:46
Оценка: :)
Здравствуйте, Erop, Вы писали:

E>Только это всё в целом бессмысленно. Сама по себе концепция exception-safe довольно бессмысленная...


Ты только что священную корову за вымя дернул. Жди беды.
Re[8]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 20.08.18 17:45
Оценка: 6 (2)
Максим Рогожин:

МР>А тот код, который я привел (обернуть 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 являются одним и тем же объектом.
Re[9]: Exception safe T Stack::pop() ?
От: uzhas Ниоткуда  
Дата: 21.08.18 10:20
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Кроме того, до C++11 было дововольно мало "безопасных" способов поюзать результат такого pop при бросающем конструкторе копирования (если рассматривать некий сферический компилятор в вакууме без каких-либо гарантий, помимо описанных в стандарте C++).


ты хочешь сказать, что из-за того, что в свежем стандарте появилось больше безопасных вариантов вызова метода T pop() проблема как бы исчезла?
она не исчезла, т.к. "опасные" варианты всё еще остались. в стандарте нет таких примеров, что вот вызывайте std::vector::at (для примера) только в одну строчку с присваиванием, иначе мы не даем гарантии на безопасность к исключениям. это ерунда какая-то, слишком уж много нюансов надо держать в голове

что ты скажешь про такие варианты вызова?
void f1(T t);
void f2(const T& t);
void f3(T&& t);

template<typename U>
void f4(U&& t) {}

int main() {
  T t;
  t = stack.pop(); // 1
  f1(stack.pop()); //2
  f2(stack.pop()); //3
  f3(stack.pop()); //4
  f4(stack.pop()); //5
}


в каких случаях C++17 нас защищает, а в каких у нас уже будут проблемы при исключениях при копировании?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.