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[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[10]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 21.08.18 14:19
Оценка: 10 (1)
uzhas:

U>ты хочешь сказать, что из-за того, что в свежем стандарте появилось больше безопасных вариантов вызова метода T pop() проблема как бы исчезла?

U>она не исчезла, т.к. "опасные" варианты всё еще остались.

"Опасные" варианты использования всегда можно придумать, в том числе и для пары top и pop:

T t = std::move(stack.top());
POTENTIALLY_THROWING_EXPRESSION;
use_value(t);
stack.pop();

Если POTENTIALLY_THROWING_EXPRESSION кинет исключение, то на вершине стека, возможно, останется лежать мусорный объект, а полезное значение топового элемента может быть "утеряно".

U>в стандарте нет таких примеров, что вот вызывайте std::vector::at (для примера) только в одну строчку с присваиванием, иначе мы не даем гарантии на безопасность к исключениям.


Тебе возможные варианты кривого использования std::vector::at привести, что ли?

U>это ерунда какая-то, слишком уж много нюансов надо держать в голове


Мда, это ж какой мегамозг надо иметь, чтоб держать в голове два действия: перемещение/копирование элемента и его удаление... С таким rocket science далеко не каждый справится.

U>что ты скажешь про такие варианты вызова?

U>
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
}

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

Если T — это кошерный тип с небросающим перемещающим конструктором и небросающим перемещающим оператором присваивания, то копирования T тут нигде не будет (внутренности f1 — f4 не рассматриваем). В этом случае возможностей C++11 уже достаточно.

В случае, если для возврата значения из pop используется потенциально бросающее копирование, C++17 даёт возможность сделать отложенный вызов деструктора для топового элемента при условии, что исключение брошено не было. Дальше при инициализации параметра t функции f1 C++17 обеспечивает guaranteed copy elision.
Отредактировано 21.08.2018 14:21 N. I. . Предыдущая версия .
Re[10]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 22.08.18 11:21
Оценка: 6 (1)
σ:

NI>>- из-за исключения при конструировании временного объекта, который компилятору позволялось создавать в контексте инициализации ссылки t (интересно, когда-либо вообще были такие компиляторы, которые этим правом пользовались?).


NI>>В C++11 правила инициализации ссылок уже более вменяемые — там можно гарантированно забайндить ссылку непосредственно на return object функции pop


σ>Почему нигде об этом не пишут?


А ты везде проверил?

σ>Как конкретно изменился wording инициализации ссылок?


C++98/03 [dcl.init.ref] / 5:

....
— If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):

    — The reference is bound to the object represented by the rvalue (see 3.10) or to a subobject within that object.

    — A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a subobject within the temporary. [Footnote: Clearly, if the reference initialization being processed is one for the first argument of a copy constructor call, an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion.]


C++11 [dcl.init.ref] / 5:

....
— If the initializer expression

    — is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

    — has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3”,

then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). ....


C++17 [dcl.init.ref] / 5.2.1:

— If the initializer expression

(5.2.1.1) — is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

(5.2.1.2) — has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 16.3.1.6),

then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” (7.5) and the temporary materialization conversion (7.4) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).

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[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[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[6]: Exception safe T Stack::pop() ?
От: kov_serg Россия  
Дата: 20.08.18 11:35
Оценка: +1
Здравствуйте, Marty, Вы писали:

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

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

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

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


Ты только что священную корову за вымя дернул. Жди беды.
Re: Exception safe T Stack::pop() ?
От: c-smile Канада http://terrainformatica.com
Дата: 22.08.18 18:28
Оценка: -1
Здравствуйте, Максим Рогожин, Вы писали:

МР>Привет!


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


Можно:

template<class T>
T Stack<T>::Pop() {
  if( vused == 0 )
    return T();
  else
    return v_[vused--];
}
Отредактировано 22.08.2018 18:43 c-smile . Предыдущая версия .
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[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[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[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() ?
От: 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[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 нас защищает, а в каких у нас уже будут проблемы при исключениях при копировании?
Re[9]: Exception safe T Stack::pop() ?
От: Максим Рогожин Россия  
Дата: 21.08.18 15:06
Оценка:
Здравствуйте, N. I., Вы писали:

NI>C++17 в добавок к этому гарантирует, что в следующем случае


NI>
auto t = stack.pop();

NI>переменная t и return object функции pop являются одним и тем же объектом.

Интересно почему это изначально сделано не было?
Re[11]: Exception safe T Stack::pop() ?
От: uzhas Ниоткуда  
Дата: 21.08.18 15:08
Оценка:
Здравствуйте, N. I., Вы писали:

NI>В случае, если для возврата значения из pop используется потенциально бросающее копирование, C++17 даёт возможность сделать отложенный вызов деструктора для топового элемента при условии, что исключение брошено не было.


это стало возможным только благодаря функции std::uncaught_exceptions, ничего другого полезного для данной задачи не появилось в C++17. собственно, об этом я и спрашивал ранее
хотя, стоит заметить, реализация относительно сложная из-за нестандартизованных SCOPE_*, но рабочая
Re[9]: Exception safe T Stack::pop() ?
От: σ  
Дата: 21.08.18 22:12
Оценка:
NI>
T const &t = stack.pop();

NI>- из-за исключения при конструировании временного объекта, который компилятору позволялось создавать в контексте инициализации ссылки t (интересно, когда-либо вообще были такие компиляторы, которые этим правом пользовались?).

NI>В C++11 правила инициализации ссылок уже более вменяемые — там можно гарантированно забайндить ссылку непосредственно на return object функции pop


Почему нигде об этом не пишут? Как конкретно изменился wording инициализации ссылок?
Re[10]: Exception safe T Stack::pop() ?
От: N. I.  
Дата: 22.08.18 11:20
Оценка:
Максим Рогожин:

МР>Интересно почему это изначально сделано не было?


Это риторический вопрос?
Re[11]: Exception safe T Stack::pop() ?
От: Максим Рогожин Россия  
Дата: 22.08.18 15:58
Оценка:
Здравствуйте, N. I., Вы писали:

МР>>Интересно почему это изначально сделано не было?


NI>Это риторический вопрос?


Ну а какой смысл реализовывать этот код
class Stack {
public:
   T pop();
};

T t = stack.pop();

как вызов конструктора копирования временного объекта, в то время как можно просто конструировать объект в памяти выделенной под t?
Re[2]: Exception safe T Stack::pop() ?
От: σ  
Дата: 24.08.18 22:30
Оценка:
Авторство кода? Лицензия? Запатентован?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.