[C++, Ненормальное программирование, Программирование] can_throw или не can_throw?
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Исключения являются частью языка C++. Неоднозначной его частью. Кто-то их принципиально не использует. Вот вообще не использует. От слова совсем. Но не мы. Поскольку считаем их весьма полезной штукой, существенно повышающей надежность кода.
К сожалению, далеко не везде исключения можно задействовать. Во-первых, исключения не бесплатны и, во-вторых, не всякий код способен "пережить" возникновение исключений.
Поэтому приходится держать исключения под контролем. Чему, на мой взгляд не сильно способствуют возможности современного C++. Ибо, как мне представляется, родные механизмы языка C++ в этой части находятся в недоразвитом состоянии.
По большому счету, у нас в распоряжении есть только спецификатор noexcept. Штука полезная, конечно, но недостаточная.
В этой статье я попробую рассказать о том, чего нам в очередной раз не хватило в C++, почему нам этого не хватило, и как мы постарались из этого выкрутиться при помощи старой чужой идеи и подручных средств.
Преамбула
В C++ есть спецификатор noexcept. Видя отметку noexcept в декларации функции/метода разработчик может понять, что вызывая эту функцию/метод исключений можно не ждать. Соответственно, используя noexcept функции/методы кода можно безопасно писать код для контекстов, в которых бросать исключения нельзя (деструкторы классов, операции swap, передаваемые в C-шный код callback-и и т.д.).
Однако, отметка noexcept хорошо видна лишь когда ты изучаешь декларации функций/методов. Но когда есть код, в котором вызывается какая-то функция/метод, то сразу не поймешь, ждать ли здесь исключений или нет. Вот, например:
void some_handler::on_read_result(
const asio::error_code & ec,
std::size_t bytes_transferred)
{
if(!ec)
{
m_data_size = bytes_transferred;
handle_data();
}
else
{...}
}
Не имея перед глазами декларации handle_data нельзя просто так определить, могут ли тут вылетать наружу исключения или не могут.
Так что спецификатор noexcept решает только первую часть проблемы: позволяет понять при написании кода можно ли вызывать конкретную функцию/метод не ожидая вылета наружу исключения.
Тогда как вторая часть — это убедится в том, бросает или не бросает исключения уже написанный ранее кусок кода, в котором вызываются те или иные методы. И вот тут лично мне не хватает наличия в C++ чего-то вроде noexcept-блока. Я бы хотел написать кусок кода и поместить этот кусок в noexcept-блок. Что-то типа:
void some_handler::on_read_result(
const asio::error_code & ec,
std::size_t bytes_transferred)
{
noexcept
{
if(!ec)
{
m_data_size = bytes_transferred;
handle_data();
}
else
{...}
}
}
А нужен этот блок чтобы получить проверку со стороны компилятора. Если в noexcept-блоке выполняются только noexcept-операции, то все хорошо. Но если какое-то из действий может бросить исключение, то компилятор выдает предупреждение, а лучше ошибку.
К сожалению, такого noexcept-блока в C++ пока нет. А раз нет, то приходится выкручиваться подручными средствами. Об одном таком самодельном средстве уже рассказывалось некоторое время назад. Сегодня же хочется рассказать о другом слепленном на коленке велосипеде, который несколько облегчил жизнь.
Проблема
Итак, есть недавно начатый свежий C++ проект, в котором исключения не только разрешены, но и используются для информирования о неожиданных проблемах. В этом проекте так же широко применяется механизм обратных вызовов (callback-ов).
Прежде всего это callback-и, которые выступают в роли completion-handler-ов для Asio. Выпускать исключения из таких callback-ов нельзя, т.к. Asio эти исключения не ловит и не обрабатывает. Соответственно, вылет исключения из completion-handler-а — это крах приложения.
Так же есть callback-и, которые отдаются в библиотеку на чистом Си. И, соответственно, оттуда так же нельзя выбрасывать исключения.
Поэтому внутри callback-а, который отдается в Asio или в C-шную библиотеку, нужно сделать try/catch, внутри которого будут выполняться нужные приложению действия, а вот выброшенные исключения будут перехватываться:
void some_handler::on_read_result(
const asio::error_code & ec,
std::size_t bytes_transferred)
{
try
{
handle_read_result(ec, bytes_transferred); // Основные действия.
}
catch(...)
{
// Хотя бы просто "проглотить" исключение.
}
}
Решение очевидное, но, к сожалению, ничто не мешает невнимательному (или уставшему) разработчику написать callback без try/catch и вызвать там метод handle_read_result. И компилятор тут нам ничем не поможет.
И, на мой взгляд, это проблема. Т.к. по мере развития проекта растет вероятность того, что одна из бросающих исключения функция/метод рано или поздно будет вызвана там, где исключения не перехватываются.
Решение в виде маркера can_throw
Решение было найдено в виде специального маркера can_throw, который передается аргументом во все прикладные функции/методы. Поэтому, если функция получает аргумент типа can_throw, то она может бросать исключения. А также вызывать другие функции/методы, которые получают can_throw.
Соответственно, если в каком-то callback-е нам приходится вызывать функцию/метод, которые требуют аргумента can_throw, то нам нужно позаботится о перехвате и обработке исключений.
А позаботится об этом нас заставит сам компилятор, т.к. маркер can_throw нельзя просто так создать и отдать в вызываемую функцию/метод. Т.е. мы не можем написать вот так:
void some_handler::handle_read_result(
can_throw_t can_throw,
const asio::error_code & ec,
std::size_t bytes_transferred)
{
... // Прикладная обработка которая может бросать исключения.
}
void some_handler::on_read_result(
const asio::error_code & ec,
std::size_t bytes_transferred)
{
// Вот так быть не должно!
handle_read_result(can_throw_t{}, ec, bytes_transferred);
}
Для того, чтобы экземпляры can_throw нельзя было создавать просто так был применен следующий подход:
class can_throw_t
{
friend class exception_handling_context_t;
can_throw_t() noexcept = default;
public:
~can_throw_t() noexcept = default;
can_throw_t( const can_throw_t & ) noexcept = default;
can_throw_t( can_throw_t && ) noexcept = default;
can_throw_t &
operator=( const can_throw_t & ) noexcept = default;
can_throw_t &
operator=( can_throw_t && ) noexcept = default;
};
Т.е. кто угодно может копировать и перемещать экземпляры типа can_throw_t, но вот создавать эти экземпляры "могут не только лишь все" (с). Для того, чтобы получить экземпляр can_throw_t следует сперва создать экземпляр типа exception_handling_context_t:
class exception_handling_context_t
{
public:
can_throw_t
make_can_throw_marker() const noexcept { return {}; }
};
а затем воспользоваться методом make_can_throw_marker()
void some_handler::on_read_result(
const asio::error_code & ec,
std::size_t bytes_transferred)
{
try
{
exception_handling_context_t ctx;
handle_read_result(ctx.make_can_throw_marker(), ec, bytes_transferred);
}
catch(...)
{}
}
Да, при этом ничто не запрещает создавать экземпляры exception_handling_context_t и без использования блоков try/catch. И можно было бы попробовать сделать более железобетонное решение. Например, функцию wrap_throwing_action, которая бы получала на вход лямбду, а внутри имела бы блок try, внутри которого бы лямбда и вызывалась. Что-то вроде:
class can_throw_t
{
// Разрешаем создание can_throw только внутри
// шаблонной функции wrap_throwing_action.
template<typename Lambda>
friend void wrap_throwing_action(Lambda &&);
can_throw_t() noexcept = default;
public:
... // Все как показано выше.
};
template< typename Lambda >
void wrap_throwing_action(Lambda && lambda)
{
try
{
lambda(can_throw_t{});
}
catch(...)
{}
}
Можно было бы и так.
Но пока мы ограничились именно показанными выше тривиальными реализациями can_throw_t и exception_handling_context_t.
Отчасти потому, что у нас callback-и и так создаются посредством специальных шаблонных функций, которые оборачивают лямбды несколькими слоями вспомогательных оберток, в том числе там есть и блок try.
Отчасти потому, что какие-то функции/методы нужно вызывать не только из callback-ов, но и из конструкторов объектов. А в конструкторах исключения разрешены, посему и создавать внутри тела конструктора дополнительный try нет смысла. Гораздо проще внутри конструктора объявить временный exception_handling_context_t и вызывать нужную функцию:
some_handler::some_handler(
std::vector<std::byte> initial_data,
std::size_t initial_data_size)
: m_data{std::move(initial_data)}
, m_data_size{initial_data_size}
{
exception_handling_context_t ctx;
handle_data(ctx.make_can_throw_marker());
}
...
void some_handler::handle_read_result(
can_throw_t can_throw,
const asio::error_code & ec,
std::size_t bytes_transferred)
{
if(!ec)
{
m_data_size = bytes_transferred;
handle_data(can_throw);
}
else
{
...
}
}
...
void some_handler::handle_data(can_throw_t)
{
... // Прикладная обработка данных.
}
Отчасти еще и потому, что для разных ситуаций нужны разные действия в catch: где-то проблемы логируются, где-то "проглатываются" (но при этом из callback-а возвращается код ошибки, а не положительный результат). Попытка запихнуть эти особенности обработки исключений в wrap_throwing_action только усложнила бы реализацию wrap_throwing_action.
Общие впечатления
Общие впечатления от использования описанного выше решения в течении двух месяцев клепания килотонн нового кода практически в режиме "без выходных и проходных" хорошие. Коэффициент спокойного сна сильно повысился. Как и обозримость кода: сразу видно, где исключения могут и будут вылетать. Причем это видно не только в местах декларации функций/методов, но и, что более важно в данном случае, в местах вызова функций/методов.
Однако, есть два момента, которые обязательно нужно подчеркнуть и которые не позволяют декларировать данное решение в качестве хоть сколько-нибудь универсального.
Во-первых, это увеличение объема кода за счет маркеров can_throw. Т.е., с одной стороны, глядя на код сразу видишь, кому разрешено бросать исключения. Но, с другой стороны, во многих функциях/методах появляется дополнительный параметр. И требуется некоторая привычка, чтобы не обращать на него внимание, если хочется разобраться с тем, что и как делает метод.
Во-вторых, накладные расходы на передачу маркера can_throw вниз по стеку вызовов не оценивались. В нашем конкретном случае такие накладные расходы, если они и есть, роли не играют. Т.к. callback-и, в которых can_throw создаются, вызываются ну максимум несколько десятков тысяч раз в секунду. И передача экземпляров can_throw внутри callback-а — это просто копейки по сравнению с выполняемой callback-ами прикладной работой (не говоря уже о стоимости операций, приводящих к вызову callback-ов).
Но вот если бы функции с маркерами can_throw стали бы вызываться миллионы раз в секунду, то накладные расходы на can_throw стоило бы оценить. Возможно, современные оптимизирующие компиляторы просто повыбрасывали бы передачу can_throw из генерируемого кода. Но сделали бы они это или нет, а если бы сделали то во всех ли случаях, — это все нужно проверять на практике.
Поэтому, как минимум, два вышеозначенных момента нужно иметь в виду тем, кто захочет применить подход с маркерами can_throw в своем коде.
Заключение
В данной статье я попытался поделится нашим свежим опытом и решением, которое несколько облегчило нам жизнь, при разработке нового кода на C++.
Но само это решение не было придумано нами. Насколько я помню, подобный подход описывался на каком-то из форумов (вроде бы это был RSDN) лет эдак 15 назад. Так что мы здесь ничего не изобретали, а лишь вспомнили про то, что кто-то придумал много лет назад.
Конечно же, было бы лучше иметь более продвинутые средства контроля за выбросом исключений в С++. Тогда бы не пришлось прибегать к велосипедам типа can_throw. Но пока есть лишь то, что есть :( И для повышения степени доверия к коду приходится собирать на коленке собственные велосипеды.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование микроконтроллеров] Отладка и программирование микроконтроллеров stm32f303, atmega328 через любой интерфейс, как через jtag
- [Программирование, Серверное администрирование] Миграция процессов из Pega в Camunda — пошаговое руководство (перевод)
- [CSS, HTML, Программирование, Разработка веб-сайтов] Визуальное сравнение 13 CSS-фреймворков
- [Open source, OpenStreetMap, Визуализация данных, Научно-популярное, Программирование] Делаем маршрутизацию (роутинг) на OpenStreetMap. Добавляем поддержку односторонних дорог
- [Swift, Программирование, Разработка мобильных приложений, Разработка под iOS] MVI и SwiftUI – одно состояние
- [JavaScript, Node.JS, Программирование, Разработка веб-сайтов] Руководство по Deno: примеры работы со средой выполнения TypeScript (перевод)
- [Будущее здесь, Интервью, Искусственный интеллект, Машинное обучение, Программирование] Необычное собеседование: GPT-3 в роли кандидата (перевод)
- [Программирование микроконтроллеров, Производство и разработка электроники] История разработки одного дозиметра (Часть 2)
- [Анализ и проектирование систем, Программирование, Проектирование и рефакторинг, Управление разработкой] Методика проектирования архитектурных слоев на основе анемичной модели и DDD
- [CSS, HTML, JavaScript, Программирование, Разработка веб-сайтов] Как стать Front-End разработчиком
Теги для поиска: #_c++, #_nenormalnoe_programmirovanie (Ненормальное программирование), #_programmirovanie (Программирование), #_c++, #_c++11, #_c++, #_nenormalnoe_programmirovanie (
Ненормальное программирование
), #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:46
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Исключения являются частью языка C++. Неоднозначной его частью. Кто-то их принципиально не использует. Вот вообще не использует. От слова совсем. Но не мы. Поскольку считаем их весьма полезной штукой, существенно повышающей надежность кода. К сожалению, далеко не везде исключения можно задействовать. Во-первых, исключения не бесплатны и, во-вторых, не всякий код способен "пережить" возникновение исключений. Поэтому приходится держать исключения под контролем. Чему, на мой взгляд не сильно способствуют возможности современного C++. Ибо, как мне представляется, родные механизмы языка C++ в этой части находятся в недоразвитом состоянии. По большому счету, у нас в распоряжении есть только спецификатор noexcept. Штука полезная, конечно, но недостаточная. В этой статье я попробую рассказать о том, чего нам в очередной раз не хватило в C++, почему нам этого не хватило, и как мы постарались из этого выкрутиться при помощи старой чужой идеи и подручных средств. Преамбула В C++ есть спецификатор noexcept. Видя отметку noexcept в декларации функции/метода разработчик может понять, что вызывая эту функцию/метод исключений можно не ждать. Соответственно, используя noexcept функции/методы кода можно безопасно писать код для контекстов, в которых бросать исключения нельзя (деструкторы классов, операции swap, передаваемые в C-шный код callback-и и т.д.). Однако, отметка noexcept хорошо видна лишь когда ты изучаешь декларации функций/методов. Но когда есть код, в котором вызывается какая-то функция/метод, то сразу не поймешь, ждать ли здесь исключений или нет. Вот, например: void some_handler::on_read_result(
const asio::error_code & ec, std::size_t bytes_transferred) { if(!ec) { m_data_size = bytes_transferred; handle_data(); } else {...} } Не имея перед глазами декларации handle_data нельзя просто так определить, могут ли тут вылетать наружу исключения или не могут. Так что спецификатор noexcept решает только первую часть проблемы: позволяет понять при написании кода можно ли вызывать конкретную функцию/метод не ожидая вылета наружу исключения. Тогда как вторая часть — это убедится в том, бросает или не бросает исключения уже написанный ранее кусок кода, в котором вызываются те или иные методы. И вот тут лично мне не хватает наличия в C++ чего-то вроде noexcept-блока. Я бы хотел написать кусок кода и поместить этот кусок в noexcept-блок. Что-то типа: void some_handler::on_read_result(
const asio::error_code & ec, std::size_t bytes_transferred) { noexcept { if(!ec) { m_data_size = bytes_transferred; handle_data(); } else {...} } } А нужен этот блок чтобы получить проверку со стороны компилятора. Если в noexcept-блоке выполняются только noexcept-операции, то все хорошо. Но если какое-то из действий может бросить исключение, то компилятор выдает предупреждение, а лучше ошибку. К сожалению, такого noexcept-блока в C++ пока нет. А раз нет, то приходится выкручиваться подручными средствами. Об одном таком самодельном средстве уже рассказывалось некоторое время назад. Сегодня же хочется рассказать о другом слепленном на коленке велосипеде, который несколько облегчил жизнь. Проблема Итак, есть недавно начатый свежий C++ проект, в котором исключения не только разрешены, но и используются для информирования о неожиданных проблемах. В этом проекте так же широко применяется механизм обратных вызовов (callback-ов). Прежде всего это callback-и, которые выступают в роли completion-handler-ов для Asio. Выпускать исключения из таких callback-ов нельзя, т.к. Asio эти исключения не ловит и не обрабатывает. Соответственно, вылет исключения из completion-handler-а — это крах приложения. Так же есть callback-и, которые отдаются в библиотеку на чистом Си. И, соответственно, оттуда так же нельзя выбрасывать исключения. Поэтому внутри callback-а, который отдается в Asio или в C-шную библиотеку, нужно сделать try/catch, внутри которого будут выполняться нужные приложению действия, а вот выброшенные исключения будут перехватываться: void some_handler::on_read_result(
const asio::error_code & ec, std::size_t bytes_transferred) { try { handle_read_result(ec, bytes_transferred); // Основные действия. } catch(...) { // Хотя бы просто "проглотить" исключение. } } Решение очевидное, но, к сожалению, ничто не мешает невнимательному (или уставшему) разработчику написать callback без try/catch и вызвать там метод handle_read_result. И компилятор тут нам ничем не поможет. И, на мой взгляд, это проблема. Т.к. по мере развития проекта растет вероятность того, что одна из бросающих исключения функция/метод рано или поздно будет вызвана там, где исключения не перехватываются. Решение в виде маркера can_throw Решение было найдено в виде специального маркера can_throw, который передается аргументом во все прикладные функции/методы. Поэтому, если функция получает аргумент типа can_throw, то она может бросать исключения. А также вызывать другие функции/методы, которые получают can_throw. Соответственно, если в каком-то callback-е нам приходится вызывать функцию/метод, которые требуют аргумента can_throw, то нам нужно позаботится о перехвате и обработке исключений. А позаботится об этом нас заставит сам компилятор, т.к. маркер can_throw нельзя просто так создать и отдать в вызываемую функцию/метод. Т.е. мы не можем написать вот так: void some_handler::handle_read_result(
can_throw_t can_throw, const asio::error_code & ec, std::size_t bytes_transferred) { ... // Прикладная обработка которая может бросать исключения. } void some_handler::on_read_result( const asio::error_code & ec, std::size_t bytes_transferred) { // Вот так быть не должно! handle_read_result(can_throw_t{}, ec, bytes_transferred); } Для того, чтобы экземпляры can_throw нельзя было создавать просто так был применен следующий подход: class can_throw_t
{ friend class exception_handling_context_t; can_throw_t() noexcept = default; public: ~can_throw_t() noexcept = default; can_throw_t( const can_throw_t & ) noexcept = default; can_throw_t( can_throw_t && ) noexcept = default; can_throw_t & operator=( const can_throw_t & ) noexcept = default; can_throw_t & operator=( can_throw_t && ) noexcept = default; }; Т.е. кто угодно может копировать и перемещать экземпляры типа can_throw_t, но вот создавать эти экземпляры "могут не только лишь все" (с). Для того, чтобы получить экземпляр can_throw_t следует сперва создать экземпляр типа exception_handling_context_t: class exception_handling_context_t
{ public: can_throw_t make_can_throw_marker() const noexcept { return {}; } }; а затем воспользоваться методом make_can_throw_marker() void some_handler::on_read_result(
const asio::error_code & ec, std::size_t bytes_transferred) { try { exception_handling_context_t ctx; handle_read_result(ctx.make_can_throw_marker(), ec, bytes_transferred); } catch(...) {} } Да, при этом ничто не запрещает создавать экземпляры exception_handling_context_t и без использования блоков try/catch. И можно было бы попробовать сделать более железобетонное решение. Например, функцию wrap_throwing_action, которая бы получала на вход лямбду, а внутри имела бы блок try, внутри которого бы лямбда и вызывалась. Что-то вроде: class can_throw_t
{ // Разрешаем создание can_throw только внутри // шаблонной функции wrap_throwing_action. template<typename Lambda> friend void wrap_throwing_action(Lambda &&); can_throw_t() noexcept = default; public: ... // Все как показано выше. }; template< typename Lambda > void wrap_throwing_action(Lambda && lambda) { try { lambda(can_throw_t{}); } catch(...) {} } Можно было бы и так. Но пока мы ограничились именно показанными выше тривиальными реализациями can_throw_t и exception_handling_context_t. Отчасти потому, что у нас callback-и и так создаются посредством специальных шаблонных функций, которые оборачивают лямбды несколькими слоями вспомогательных оберток, в том числе там есть и блок try. Отчасти потому, что какие-то функции/методы нужно вызывать не только из callback-ов, но и из конструкторов объектов. А в конструкторах исключения разрешены, посему и создавать внутри тела конструктора дополнительный try нет смысла. Гораздо проще внутри конструктора объявить временный exception_handling_context_t и вызывать нужную функцию: some_handler::some_handler(
std::vector<std::byte> initial_data, std::size_t initial_data_size) : m_data{std::move(initial_data)} , m_data_size{initial_data_size} { exception_handling_context_t ctx; handle_data(ctx.make_can_throw_marker()); } ... void some_handler::handle_read_result( can_throw_t can_throw, const asio::error_code & ec, std::size_t bytes_transferred) { if(!ec) { m_data_size = bytes_transferred; handle_data(can_throw); } else { ... } } ... void some_handler::handle_data(can_throw_t) { ... // Прикладная обработка данных. } Отчасти еще и потому, что для разных ситуаций нужны разные действия в catch: где-то проблемы логируются, где-то "проглатываются" (но при этом из callback-а возвращается код ошибки, а не положительный результат). Попытка запихнуть эти особенности обработки исключений в wrap_throwing_action только усложнила бы реализацию wrap_throwing_action. Общие впечатления Общие впечатления от использования описанного выше решения в течении двух месяцев клепания килотонн нового кода практически в режиме "без выходных и проходных" хорошие. Коэффициент спокойного сна сильно повысился. Как и обозримость кода: сразу видно, где исключения могут и будут вылетать. Причем это видно не только в местах декларации функций/методов, но и, что более важно в данном случае, в местах вызова функций/методов. Однако, есть два момента, которые обязательно нужно подчеркнуть и которые не позволяют декларировать данное решение в качестве хоть сколько-нибудь универсального. Во-первых, это увеличение объема кода за счет маркеров can_throw. Т.е., с одной стороны, глядя на код сразу видишь, кому разрешено бросать исключения. Но, с другой стороны, во многих функциях/методах появляется дополнительный параметр. И требуется некоторая привычка, чтобы не обращать на него внимание, если хочется разобраться с тем, что и как делает метод. Во-вторых, накладные расходы на передачу маркера can_throw вниз по стеку вызовов не оценивались. В нашем конкретном случае такие накладные расходы, если они и есть, роли не играют. Т.к. callback-и, в которых can_throw создаются, вызываются ну максимум несколько десятков тысяч раз в секунду. И передача экземпляров can_throw внутри callback-а — это просто копейки по сравнению с выполняемой callback-ами прикладной работой (не говоря уже о стоимости операций, приводящих к вызову callback-ов). Но вот если бы функции с маркерами can_throw стали бы вызываться миллионы раз в секунду, то накладные расходы на can_throw стоило бы оценить. Возможно, современные оптимизирующие компиляторы просто повыбрасывали бы передачу can_throw из генерируемого кода. Но сделали бы они это или нет, а если бы сделали то во всех ли случаях, — это все нужно проверять на практике. Поэтому, как минимум, два вышеозначенных момента нужно иметь в виду тем, кто захочет применить подход с маркерами can_throw в своем коде. Заключение В данной статье я попытался поделится нашим свежим опытом и решением, которое несколько облегчило нам жизнь, при разработке нового кода на C++. Но само это решение не было придумано нами. Насколько я помню, подобный подход описывался на каком-то из форумов (вроде бы это был RSDN) лет эдак 15 назад. Так что мы здесь ничего не изобретали, а лишь вспомнили про то, что кто-то придумал много лет назад. Конечно же, было бы лучше иметь более продвинутые средства контроля за выбросом исключений в С++. Тогда бы не пришлось прибегать к велосипедам типа can_throw. Но пока есть лишь то, что есть :( И для повышения степени доверия к коду приходится собирать на коленке собственные велосипеды. =========== Источник: habr.com =========== Похожие новости:
Ненормальное программирование ), #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:46
Часовой пояс: UTC + 5