Re[7]: 1) Родовая травма: исключеиния в деструкторах
От: Erop Россия  
Дата: 13.10.12 06:37
Оценка:
Здравствуйте, enji, Вы писали:

E>а что в этом плохого? Оставшийся поток = ошибка программиста. ПРограмма нам о ней и сообщает...


Кстати, почему не assert?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: rg45 СССР  
Дата: 13.10.12 07:06
Оценка: :)
Здравствуйте, Erop, Вы писали:


E>Как же быть? Можно попробовать написать так:
shared_pair<T1, T2> p( shared_ptr<T1>( new T1 ), shared_ptr<T2>( new T2 ) );


E>К сожалению, это ничем не лучше. Компилятор волен сделать так.

E>создать Т1, создать Т2, потом создать умный указатель на первый, потом на второй и только потом вызвать конструктор пары. Соответственно, исключение в конструкторе Т2 сломает этот код не хуже первого.

Мог бы ты подкрепить этот тезис какими-то ссылками на стандарт или практическим примером? Пример можешь запустить у себя в домашних условиях, а нам просто расскажешь, подтвердилось или нет, и на каких компиляторах, мы тебе поверим на слово.

Я думаю, ты ошибаешься. Если бы мы рассматривали выражение вида foo(a + x, a + y), то да — поскольку в этом случае выражения аргументов не разделены точками следования. А выражение вида foo(a(x), a(y)) — совсем другое дело — аргументы здесь инициализируются с использованием конструкторов, вход и выход в/из которых разделены точками следования, а это значит, что компилятор не приступит вычислению следующего аргумента, пока не закончит вычисление предыдущего.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[8]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 07:41
Оценка:
Здравствуйте, Erop, Вы писали:

E>>а что в этом плохого? Оставшийся поток = ошибка программиста. ПРограмма нам о ней и сообщает...

E>... раз две недели роняя ответственный сервер клиента в непонятный момент по терминэйт, без логов и сохранения данных. Прекрасное решение, демонстрирующее всю мощь RAII в кривых руках...

Precondition failure

Precondition failures indicate coding errors, which are non-recoverable conditions. Exceptions are for recoverable conditions. No point in unwinding if you won’t be able to recover. In general, you should only throw in response to a precondition failure if you can’t convince your customer that further arbitrarily erroneous (aka undefined) behavior is worse than termination.


Для логов есть set_terminate. Если допустим auto-join — сделать небольшую обвёртку.
Если важно не терять агонизирующий процесс — можно попробовать так.
#include <boost/test/included/prg_exec_monitor.hpp> 
#include <boost/test/execution_monitor.hpp>
#include <exception>
#include <iostream>
#include <ostream>
 
using namespace std;
 
void myterminate ()
{
  cerr << "terminate handler called\n";
}
int terminator()
{
    terminate();
}
int cpp_main( int, char* [] )
{
    set_terminate(myterminate);
    boost::execution_monitor m;
    try
    {
        m.execute(terminator);
    }
    catch ( boost::execution_exception const& ex )
    {
        cout << "Caught exception: " << ex.what() << endl;
    }
    cout << "Alive!" << endl;
    return 0;
}
Re[4]: C++ и RAII
От: Ku-ku  
Дата: 13.10.12 07:42
Оценка:
Здравствуйте, andyp, Вы писали:

A>Финализация работы с ресурсом может быть разной в зависимости от того, что с ним произошло.


Например?

A>>>3. есть такие ресурсы с которыми непонятно что делать в деструкторе — ну типа нитей или файлов, про которые народ писал


KK>>Закрывать хэндлы.

KK>>Пластмассовое мусорное ведро предназначено для утилизации мусора, при попытке сварить в нём кофе оно может расплавиться. Это серьёзный недостаток ведра или не?

A>Я не против. Я за то, чтобы иметь возможность снова обратиться к ресурсу после такого вызова деструктора.


То есть закрыли хэндл и после обращаемся к ресурсу?

A>Смысл поста в том, что про RAII говорят как о технике, позволяющей автоматически решать проблему освобождения ресурсов, но на мой взгляд в сложных ситуациях это не так.


Сложная ситуация — это когда "освобождение ресурса" таковым на самом деле не является?

A>Мало того, RAII обертки могут быть бесполезны для некоторых ресурсов.


Ну если API кривое, то да, выпрямить его не всегда возможно.
Re[2]: C++ и RAII
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 08:03
Оценка:
Здравствуйте, andyp, Вы писали:

A>2. Когда попадаем в деструктор не совсем ясно, то ли мы раскручаем стек из-за исключения, связанного с нашим ресурсом, то ли все нормально (ну те нет исключения), то ли исключение вызвано чем-то другим (не нашим ресурсом).


Наше/не наше проверяется флагом (мы же знаем когда сами кидаем).
Есть/нет исключение, с помощью Unwinding Aware Destructor
struct Foo
{
    UNWINDING_AWARE_DESTRUCTOR(Foo,due_to_unwinding)
    {
        if(due_to_unwinding)
            // ...
        else
            // ...
    }
};

И пример.

A>Конечно в с++11 есть current_exception(), но проверять и отлавливать свои исключения...


для проверки "is_unwinding" из деструктора — current_exception не пригоден:

18.8.5/8
current_exception Returns: An exception_ptr object that refers to the currently handled exception

Например MSVC2012 возвращает default constructed exception_ptr в деструкторе во время раскрутки.
Re[7]: 1) Родовая травма: исключения в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 08:22
Оценка:
Здравствуйте, sts, Вы писали:

sts>2 — на уровне компилятора сделать стек исключений как, например, это сделано в дельфи — там нет проблемы исключений в деструкторе — он протсо в стек запихиватся в случае наслоения, а потом есть волзможность все достать или обработать блоками кэтч соответствующего уровня вложенности.


Стэк исключений уже есть — компиляторы умеют с ним работать. Просто в стандарте std::terminate и всё тут.
А теперь внимание, беременных и детей убрать от мониторов: 500 <b>одновременных</b> не пойманных исключений!
Re[3]: C++ и RAII
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 08:40
Оценка:
Здравствуйте, Erop, Вы писали:

R>>Что-то, Егор, я не улавливаю сути вопроса. Ты хочешь, что бы мы совместными усилиями провели четкую границу применимости RAII?

E>Ну можно сказать и так, в ответах на это сообщение я привёл пару примеров трудностей для RAII в С++
E>1) RAII откладывает нетривиальные действия на деструктор, а там в С++ с нетривиальными действиями проблемы имени CFile::Close

RAII не откладывает нетривиальные действия на деструктор, а только release. fclose не фейлится в плане освобождения(release) хэндла.
Проблема с нетривиальными отложенными действиями есть, но это относится к декструкторам, а никак ни к RAII.
Re[5]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 13.10.12 08:46
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Например, очевидно, что вместо вот этого:

EP>
EP>{
EP>    File a(/*...*/),b(/*...*/);
EP>    /* ... */
EP>    /* use a and b*/
EP>    b.flush();
EP>    a.flush();
EP>}
EP>
неплохо было бы иметь возможность писать

EP>
EP>{
EP>    File a(/*...*/),b(/*...*/);
EP>    /* ... */
EP>    /* use a and b*/
EP>}
EP>
С сохранением семантики: если b.flush() кидает, то a.flush() не вызывается, деструкторы обоих вызываются всегда.


Мне это не очевидно. Здесь нарушается принцип "одна задача — одна функция", что ИМХО усложняет понимание кода. От использования

{
    File a(/*...*/),b(/*...*/);
    scope(success)
    {
        b.flush();
        a.flush();
    }
    /* ... */
    /* use a and b*/
}

я бы не отказался. Многофункциональные бросающие деструкторы в топку.
Re[3]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: Ku-ku  
Дата: 13.10.12 08:46
Оценка: 10 (1) +1
Здравствуйте, rg45, Вы писали:

E>>Как же быть? Можно попробовать написать так:
shared_pair<T1, T2> p( shared_ptr<T1>( new T1 ), shared_ptr<T2>( new T2 ) );


E>>К сожалению, это ничем не лучше. Компилятор волен сделать так.

E>>создать Т1, создать Т2, потом создать умный указатель на первый, потом на второй и только потом вызвать конструктор пары. Соответственно, исключение в конструкторе Т2 сломает этот код не хуже первого.

R>Мог бы ты подкрепить этот тезис какими-то ссылками на стандарт


Except where noted, evaluations of operands of individual operators and of subexpressions of individual
expressions are unsequenced.

Тут как раз этот случай. Но как я уже сказал, в случае с инициализацией p достаточно просто заменить круглые скобки на фигурные.

R>А выражение вида foo(a(x), a(y)) — совсем другое дело — аргументы здесь инициализируются с использованием конструкторов, вход и выход в/из которых разделены точками следования


Точек следования больше нет

R>а это значит, что компилятор не приступит вычислению следующего аргумента, пока не закончит вычисление предыдущего.


Откуда это следует? Известно только то, что каждый конструктор будет вызван после вычисления соответствующего аргумента. Взаимный порядок вычисления этих аргументов между собой не определён.
Re[6]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: Roman Odaisky Украина  
Дата: 13.10.12 08:52
Оценка:
Здравствуйте, Ku-ku, Вы писали:

RO>>Так если помнить правило — результат new всегда немедленно помещать в именованный умный указатель — всё вполне ясно и просто:


KK>А если помнить другие правила, то можно поступить ещё проще :-)


KK>
typedef std::unique_ptr<X> U;
std::pair<U, U> p{U(new X(1)), U(new X(2))};

А в списках инициализации есть некая особенная магия, которая позволит избежать типичной проблемы такого подхода?

1. Компилятор решил вызвать вещи в таком порядке: operator new, X::X, operator new, X::X, U::U, U::U, pair::pair;
2. Второй конструктор X бросил исключение;
3. К первому new никто не вызвал delete.
До последнего не верил в пирамиду Лебедева.
Re[4]: C++ и RAII
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 08:53
Оценка:
Здравствуйте, andyp, Вы писали:

A>>>1. Хрень случается в одном месте кода, а обрабатывать ее приходится в другом.

E>>Это интересно. Можешь привести пример того, что конкретно ты имеешь в виду. Пример можно несерьёзный, просто что бы проиллюстрировать мысль.
A>На счет кода сразу не соображу. Идея состоит в том, чтобы на выходе из скопа до вызова деструктров иметь возможность прицепить нечто типа лямбда-функции, имеющей доступ к переменным, определенным в скопе. В этом случае решение проблемы будет определяться по соседству с проблемным блоком кода. Пример такой — пусть, например, ресурс — сокет. Используется обычная с-шная библиотека, возвращающая коды ошибки при работе с сокетами. Тогда в этой "лямбде" можно будет проанализировать код ошибки и предпринять адекватные действия. Плюс отпадает надобность в написании RAIIшной обертки.

Звучит похоже на BOOST_SCOPE_EXIT, SCOPE_FAILURE, SCOPE_SUCCESS:
{
    SCOPE_FAILURE(void) {
        log("could not update user info");
    } SCOPE_FAILURE_END

    File passwd("/etc/passwd");

    SCOPE_SUCCESS( (&passwd) ) {
        passwd.flush();
    } SCOPE_SUCCESS_END

    BOOST_SCOPE_EXIT( (&passwd) ) {
        passwd.close();
    } BOOST_SCOPE_EXIT_END

    // ...
}
Re[3]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: Erop Россия  
Дата: 13.10.12 08:57
Оценка: 10 (1)
Здравствуйте, rg45, Вы писали:

R>Мог бы ты подкрепить этот тезис какими-то ссылками на стандарт или практическим примером? Пример можешь запустить у себя в домашних условиях, а нам просто расскажешь, подтвердилось или нет, и на каких компиляторах, мы тебе поверим на слово.


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

Этот код (std тогда ещё не было )
#include "stdafx.h"

#include <iostream.h>


struct TestObj {
   virtual ~TestObj() {}
   TestObj() { cout << "Start build obj at 0x" << hex << (int)this << endl; }   
};

struct ThrowInCtor : TestObj {
   ThrowInCtor() { throw 1; }
};

struct ObjOwner {
   ObjOwner( TestObj* p ) { cout << "Take obj: 0x" << hex << (int)p << endl; }
};

void foo( ObjOwner a1, ObjOwner a2 ) {}



int main(int argc, char* argv[])
{
   try {
      foo( new ThrowInCtor, new TestObj );
   } catch( int i ) {
      cout << i << " catched" << endl;
      return i;
   }

    
    return 0;
}
собранный в дефолтных настройках консольного приложения даёт такой вывод:
Start build obj at 0x220030
Start build obj at 0x2217d0
1 catched
а если стереть строчку
   ThrowInCtor() { throw 1; }
то такой:
Start build obj at 0x430030
Start build obj at 0x4317d0
Take obj: 0x430030
Take obj: 0x4317d0


R>Я думаю, ты ошибаешься. Если бы мы рассматривали выражение вида foo(a + x, a + y), то да — поскольку в этом случае выражения аргументов не разделены точками следования. А выражение вида foo(a(x), a(y)) — совсем другое дело — аргументы здесь инициализируются с использованием конструкторов, вход и выход в/из которых разделены точками следования, а это значит, что компилятор не приступит вычислению следующего аргумента, пока не закончит вычисление предыдущего.

Вовсе и нет, это ничего такого не значит.
Например, в таком вот коде
for( int i = 0; i < bib_number; i++ ) {
    MyClass p( x+y+z+QQQ );
    p.Do( i );
}
компилятор может вынести вычисление x+y+z+QQQ не то, что пораньше, а вообще за пределы цикла. И никакая точка следования на входе в конструктор ему не мешает
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: Roman Odaisky Украина  
Дата: 13.10.12 08:57
Оценка:
KK>>А если помнить другие правила, то можно поступить ещё проще :-)
KK>>std::pair<U, U> p{U(new X(1)), U(new X(2))};
RO>А в списках инициализации есть некая особенная магия, которая позволит избежать типичной проблемы такого подхода?

Прочитал твое сообщение ниже, магия действительно обнаружена. Интересно. Но опасно — вдруг кто-нибудь порефакторит pair p { X, Y } в pair p(X, Y), что, казалось бы, одно и то же...
До последнего не верил в пирамиду Лебедева.
Re[4]: C++ и RAII
От: Erop Россия  
Дата: 13.10.12 08:59
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>RAII не откладывает нетривиальные действия на деструктор, а только release. fclose не фейлится в плане освобождения(release) хэндла.

EP>Проблема с нетривиальными отложенными действиями есть, но это относится к декструкторам, а никак ни к RAII.

RAII можно не тока в С++...
Вся эта возня с деструкторами -- конкретная фишка сочетания RAII и C++.
О том и речь, как бы...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[8]: 1) Родовая травма: исключеиния в деструкторах
От: enji  
Дата: 13.10.12 09:01
Оценка: 1 (1) +1
Здравствуйте, Erop, Вы писали:

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


E>>а что в этом плохого? Оставшийся поток = ошибка программиста. ПРограмма нам о ней и сообщает...


E>Кстати, почему не assert?

наверное чтобы работало и в релизе...
Re[8]: 1) Родовая травма: исключеиния в деструкторах
От: enji  
Дата: 13.10.12 09:04
Оценка: +1
Здравствуйте, Erop, Вы писали:

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


E>>а что в этом плохого? Оставшийся поток = ошибка программиста. ПРограмма нам о ней и сообщает...


E>... раз две недели роняя ответственный сервер клиента в непонятный момент по терминэйт, без логов и сохранения данных. Прекрасное решение, демонстрирующее всю мощь RAII в кривых руках...

Ну дык мозг тоже нужен. Хочется логов — сделай свою обертку над thread и приверни в деструктор логи, проблем нет... Библиотечный класс всяко не может ничего логировать.

ИМХО, на уровне библиотеки — вполне нормальная реализация. Ошибся программист — библиотека выдала ошибку. Да, с++ не от всех ошибок позволяет восстановиться. Ну дык если надо что-то неубиваемое — создаем отдельный вотчдог, который перезапускает рабочий процесс, если тот перестал подавать признаки жизни.
Re[8]: 2) Будем бдительны: pair<shared_ptr<T1>, shared_ptr<T1>>
От: Ku-ku  
Дата: 13.10.12 09:06
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>вдруг кто-нибудь порефакторит pair p { X, Y } в pair p(X, Y)


Ноги поотрывать, уши пооборвать.
Re[6]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 09:15
Оценка:
Здравствуйте, Ku-ku, Вы писали:

EP>>Например, очевидно, что вместо вот этого:

EP>>
EP>>{
EP>>    File a(/*...*/),b(/*...*/);
EP>>    /* ... */
EP>>    /* use a and b*/
EP>>    b.flush();
EP>>    a.flush();
EP>>}
EP>>
неплохо было бы иметь возможность писать

EP>>
EP>>{
EP>>    File a(/*...*/),b(/*...*/);
EP>>    /* ... */
EP>>    /* use a and b*/
EP>>}
EP>>
С сохранением семантики: если b.flush() кидает, то a.flush() не вызывается, деструкторы обоих вызываются всегда.

KK>Мне это не очевидно. Здесь нарушается принцип "одна задача — одна функция", что ИМХО усложняет понимание кода. От использования

Видимо лучше переименовать
TWO_STAGE_DESTRUCTOR_DEFERRED -> DEFERRED_ACTION
TWO_STAGE_DESTRUCTOR_RELEASE -> RESOURCE_RELEASE
и для симметрии конструктор -> RESOURCE_ACQUISITION
Одна задача — одна функция.

KK>
{
KK>    File a(/*...*/),b(/*...*/);
KK>    scope(success)
KK>    {
KK>        b.flush();
KK>        a.flush();
KK>    }
KK>    /* ... */
KK>    /* use a and b*/
KK>}

KK>я бы не отказался.

Сейчас такой синтаксис:
{
    File a(/*...*/),b(/*...*/);
    SCOPE_SUCCESS(&a,&b)
    {
        b.flush();
        a.flush();
    } SCOPE_SUCCESS_END
    /* ... */
    /* use a and b*/
}

KK>Многофункциональные бросающие деструкторы в топку.

То что они многофункциональные и бросающие — это деталь реализации, которая скрыта за слоем абстракции.
Пользователь реализует DEFERRED_ACTION и RESOURCE_RELEASE по отдельности.
Re[9]: 1) Родовая травма: исключеиния в деструкторах
От: Erop Россия  
Дата: 13.10.12 09:18
Оценка:
Здравствуйте, enji, Вы писали:

E>Ну дык мозг тоже нужен. Хочется логов — сделай свою обертку над thread и приверни в деструктор логи, проблем нет... Библиотечный класс всяко не может ничего логировать.


Логов захочется из других ниток, а им не дадут сброситься, потому, что терминируют
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: C++ и RAII
От: Evgeny.Panasyuk Россия  
Дата: 13.10.12 09:27
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Evgeny.Panasyuk, Вы писали:


EP>>RAII не откладывает нетривиальные действия на деструктор, а только release. fclose не фейлится в плане освобождения(release) хэндла.

EP>>Проблема с нетривиальными отложенными действиями есть, но это относится к декструкторам, а никак ни к RAII.
E>RAII можно не тока в С++...

Я знаю что в D, wikipedia говорит что ещё в Ada.

E>Вся эта возня с деструкторами -- конкретная фишка сочетания RAII и C++.

E>О том и речь, как бы...

В языке D тоже деструкторы.
О чём речь?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.