LINUX.ORG.RU

Как предполагают одну точку выхода?

 , ,


1

2

Во всех мануалах громко и нецензурно кричат о том, что надо писать строго одну точку выхода из функции в самом ее конце, мол так надежнее - все unlock'и в одном месте, нельзя напортачить с забытым разлоком и проч.

А скажите, господа, а как предполагается тогда писать такие функции? Через миллионы вложенных if'ов? А как это поможет читабельности и надежности?

               }
              }
             }
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
}
См. https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)

Собственно код, на котором мне бомбануло:

static YGGBool compare_records_str(CML_Node * haystack1, CML_Node * haystack2, char * needle)
{
    CML_Node * finder1;
    CML_Node * finder2;

    if (CML_NodeFindString(haystack1, needle, &finder1)) return YGG_FALSE;
    if (CML_NodeFindString(haystack2, needle, &finder2)) return YGG_FALSE;

    if (!finder1->data.string) return YGG_FALSE;
    if (!finder2->data.string) return YGG_FALSE;

    if (strcmp(finder1->data.string, finder2->data.string)) return YGG_FALSE;

    return YGG_TRUE;
}

★★★★★

Ответ на: комментарий от anonymous

RAII любят ровно до тех пор, пока не нужно проверять код возврата функций вроде «unlock» «my_extlib_free», потом приходит озарение «listen words of wisdom, write in C», так как обычный и всем понятный код на С становится понятнее, сопровождаемее чем RAII-лапша. Не всегда однострочник это хорошо.

nikitos ★★★
()
Ответ на: комментарий от vzzo

Зависит от контекста. Если close вернул ошибку перед тем, как я делаю rename (.tmp, file), то не буду делать rename.

kirk_johnson ★☆
()

Во всех мануалах громко и нецензурно кричат о том, что надо писать строго одну точку выхода из функции в самом ее конце

Во-первых ты читаешь тухлые мануалы.

код, на котором мне бомбануло

Во-вторых, ты нифига в них не понял. В этом коде одна точка выхода. Точка выхода - это куда передается управление. В данном случае - место из которого был вызов функции.

мол так надежнее - все unlock'и в одном месте

Всё правильно. Ресурсы ты получаешь перед входом в функцию, передаёшь в функцию в виде параметров и после возврата из функции чистишь.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от kirk_johnson

1. В Си нет экзепшнов.
2. Если unlock или close кидают exception - это уже смахивает на кривую библиотеку
3. Если используешь то, что бросает экзепшны, поймать саммоу не судьба?

invy ★★★★★
()
Ответ на: комментарий от invy

1. Капитан очевидность в треде! 2. Ошибки записи на close() вполне могут быть. Игнорировать? 3. Почитай про эксцепшоны в деструкторах, бога ради.

kirk_johnson ★☆
()
Ответ на: комментарий от invy

Ниче, что C++ уже 34 года? Не?

Я смотрю, весенние каникулы уже начались.

Тот C++, которому бы исполнилось 34 года, уже давно приколочен осиновыми кольями к гробу, закопан в землю на глубину 9 футов и завален гигантской могильной плитой с надписью «Не вскрывать! Вы еще молодые, шутливые.»

И да,

Stable release	
ISO/IEC 14882:2014 / 15 December 2014; 2 years ago
LamerOk ★★★★★
()
Ответ на: комментарий от kirk_johnson

Ошибки записи на close() вполне могут быть. Игнорировать?

Одна только маленькая проблема: об ошибках на close() можно только сообщать, сделать всё равно толком уже ничего нельзя, кроме как откатиться.

vodz ★★★★★
()
Ответ на: комментарий от kirk_johnson

Я тебе привел случай, где это может быть важно.

Мне? Вы с кем-то меня спутали. Я вам не возражал. Я лишь уточнил, почему на ошибку на close часто забивают. Последующий remane - это уже прикладуха — надстройка над unix-way.

vodz ★★★★★
()
Ответ на: комментарий от LamerOk

Судя по всему - да. Набежала школота неумеющая читать.

invy ★★★★★
()
Ответ на: комментарий от kirk_johnson

И вообще судя по всему у тебя проблема с логикой. Либо ты сам велосипедишь обертку над си чтобы raii работало - тогда экзепшнов не будет. Либо ты используешь плюсовые библиотеки, но в таком случае разработчик библиотеки позаботился об экзепшнах. И тебе не надо велосипедить обертки.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 1)
Ответ на: комментарий от no-such-file

но именно для этого в кресты впихнули исключения

Исключения в кресты вбросили, чтобы сделать плюсовую альтернативу setjmp/longjmp (если обработка сбоя невозможна логически в текущем фрейме стека). Конструкторы тут не причём. Конструктор нужен, чтобы сконструировать валидный объект, а не делать работу объекта. Работу делают методы.

anonymous
()
Ответ на: комментарий от invy

Что мешает сделать catch(...)

catch означает, что исключение не должно раскручиваться дальше. Исключения делали, чтобы обрабатывать ошибку на глубоких слоях стека. В текущем кадре стека у нас может не быть других идей, кроме как выдать сообщение и грохнуть программу. А надо не валить программу по любому поводу а отменять/повторять задачу высокого уровня (транзакцию).

Программист должен решать в каком фрейме обрабатывать ошибку, а не баги в языке, которые уже десятки лет не чинят.

anonymous
()
Ответ на: комментарий от anonymous

а не баги в языке, которые уже десятки лет не чинят.

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

vodz ★★★★★
()
Ответ на: комментарий от anonymous

Конструктор нужен, чтобы сконструировать валидный объект, а не делать работу объекта

Да. И как конструктор должен сигнализировать, что не хватило памяти для размещения объекта? Вот поэтому и прикрутили исключения и поэтому new бросает исключение при нехватке памяти.

сделать плюсовую альтернативу setjmp/longjmp

На это вообще всем начхать.

no-such-file ★★★★★
()
Ответ на: комментарий от LamerOk

У тебя наверно и C - пятилетний мальчик?

anonymous
()
Ответ на: комментарий от anonymous

Да, catch(...) в деструкторе похож на кстыль. Но всё равно лучшее, чем всё остальное, мне кажется.

В конце концов есть std::exception_ptr current_exception(); Передаешь в конструкторе std::exception_ptr, после деструктора смотришь что в нем.

#include <exception>
#include <stdexcept>
#include <iostream>

class A {
public:
  std::exception_ptr& m_eptr;
  A(std::exception_ptr& eptr) : m_eptr(eptr) { }
  ~A() { try { std::string().at(1); } catch(...) { m_eptr = std::current_exception(); } }
};

int main()
{
  std::exception_ptr eptr;
  {
    A res(eptr);
  }
  try {
    if(eptr)
    {
      std::rethrow_exception(eptr);
    }
  } catch (const std::exception& e) {
    std::cout << e.what() << "\n";
  }
}

invy ★★★★★
()
Ответ на: комментарий от anonymous

У Вас нога прострелена. https://isocpp.org/wiki/faq/exceptions#ctors-can-throw

А у Вас голова.

https://isocpp.org/wiki/faq/exceptions#ctors-can-throw

How can I handle a constructor that fails? Throw an exception.

Лучше ничего не бросать в C++ из конструктора

В faq написано иначе.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

В faq написано иначе.

дочитываем до ссылке, как это делать: https://isocpp.org/wiki/faq/exceptions#selfcleaning-members

Там написано, что единственный правильный метод обработки ошибок в конструкторе - кидать исключения. Чтобы эти исключения были безопасными (не было утечек ресурсов и памяти), надо вручную освободить все ресурсы, т.к. деструктор не будет вызван (недопустить зомби).

Включать или декорировать такой класс сложнее: чтобы перехватить это исключение нельзя использовать.

Ручная работа с ресурсами для C++ - больше строчек и менее понятный код в сравнении с сишным стилем.

Простое решение - делать конструкторы и деструкторы безопасными (без исключений). Отдельная функция init для работы с ресурсами (и вызов её при начале реальной работы и скоуп для обработки успешного/ошибочного завершения убирает сложность этой проблемы.

RAII - для работы с памятью (где ошибки во всех программах крашат процесс), мьютексами/семаформами. Не для файловых операций (особенно сетевых), соединений к базам данных, сложных протоколов.

anonymous
()
Ответ на: комментарий от anonymous

вручную освободить все ресурсы

Нет, там написано, что

Every data member inside your object should clean up its own mess.

Деструктор самого класса не вызовется, да, а вот деструкторы членов - вызовутся. Они и должны освобождать ресурсы.

anonymous
()
Ответ на: комментарий от anonymous

Они и должны освобождать ресурсы.

fclose() вернуло в деструкторе EIO, теперь:
Write a message to a log-file. Terminate the process. Or call Aunt Tilda. But do not throw an exception!

Или C++ сам поможет: terminate called after throwing an instance of 'std::logic_error'

#include <stdexcept>
struct A { ~A() throw(std::logic_error) { throw std::logic_error("OOPS"); } };
struct B { A a; B() { throw std::logic_error("Bad constructor"); } };

int main() { try { B b; } catch(std::logic_error) { return 1; } }

anonymous
()
Ответ на: комментарий от anonymous

Всё, что ты пишешь — не соответствует действительности и тому, что написано по приводимым тобой ссылкам.

utf8nowhere ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.