Comments 36
А зачем boost, если в C++11 есть std::function?
+9
Здорово, но пока нет уверенности в корректной обработке эксепшнов, неюзабельно, если надумаете, как поправить, будет здорово, самому этого не хватает в C++.
0
Что-то мне подсказывает, что тут её и не может быть. Ну разве что в деструкторе вызов функтора обложить try-catch'ем, и не выпускать возможные исключения за пределы деструктора.
0
Мне так видится, что закрывающий код вообще не должен бросать эксепшены, та же проблема может быть в любом деструкторе. Можно, конечно, вызывать этот блок в пустом try..catch, вот только это совсем не корректно.
+2
Зато безопасно. :)
-2
В деструкторе бросать наверно таки не хорошо.
Но код, который будет выполнятся при выходе из функции, не обязательно будет заниматся очисткой ресурсов.
И вот тут то лучше не ловить эксепшены, ибо черезмерное увлечение перехватом всего и вся потом выливается в долгую и геморойную отладку, когда приложение вроде и не валится, но работает как-то не так.
Ну разве что в какой-то области, где подвергаются опасности жизни людей — лучше перехватить лишний раз исключение и как-то попытатся продолжить работу, чем упасть.
Вобщем надо ли там оборачивать в try/catch — сильно зависит от того, что там делается.
Как вариант, можно завести дав типа класса — один безопасный — с обёртками, второй не безопасный.
Но код, который будет выполнятся при выходе из функции, не обязательно будет заниматся очисткой ресурсов.
И вот тут то лучше не ловить эксепшены, ибо черезмерное увлечение перехватом всего и вся потом выливается в долгую и геморойную отладку, когда приложение вроде и не валится, но работает как-то не так.
Ну разве что в какой-то области, где подвергаются опасности жизни людей — лучше перехватить лишний раз исключение и как-то попытатся продолжить работу, чем упасть.
Вобщем надо ли там оборачивать в try/catch — сильно зависит от того, что там делается.
Как вариант, можно завести дав типа класса — один безопасный — с обёртками, второй не безопасный.
0
Когда-то давно написал:
Использовать, например так:
Или так:
class call_func_when_out_of_scope {
public:
call_func_when_out_of_scope(boost::function<void()> func_to_call_in_destructor)
: f(func_to_call_in_destructor)
{}
~call_func_when_out_of_scope() {
if (!f.empty())
f();
}
protected:
boost::function<void()> f;
};
Использовать, например так:
void test(int i)
{
printf("in test, i=%d!\n", i);
}
int main()
{
call_func_when_out_of_scope h(boost::bind(test, 42));
printf("out of main()!\n");
return 0;
}
/* Output:
out of main()!
in test, i=42!
*/
Или так:
{
...
if (FILE* f = fopen("foo_bar.data", "r")) {
call_func_when_out_of_scope close_file(boost::bind(fclose, f));
....
}
}
0
Недостаток вашей реализации в том, что вызвать можно только какую-то отдельную функцию, в моей-же можно вызвать просто произвольный код. Хотя некоторые вещи, такие как ваш второй пример, всё-же удобнее делать вашей.
0
Какие проблемы? Код в лямбду заворачивать — и вперёд. Функтор он и в Африке функтор.
+2
Ну в таком случае наши решения весьма похожи ), отличие только в макросах, которые добавляют немного удобства.
0
Вот конкретно вашу реализацию я бы не стал использовать в своих проектах: в ней, во-первых, «голые new/delete» (что сразу вызывает опасения), во-вторых, вызовы виртуальных функций (хотя реализация всегда существует в одном-единственном виде, откуда тут полиморфизм взялся?).
Это сразу вызывает вопросы, а для «маленьких утилитарных классов» это недопустимо.
Это сразу вызывает вопросы, а для «маленьких утилитарных классов» это недопустимо.
+1
Если вы дочитали статью до конца, то могли увидеть что в последней версии, ни new, ни delete, ни виртуализация напрямую не используется.
Другое дело что, если мне не изменяется память, тот же std::function именно через виртуализацию и сделан.
Другое дело что, если мне не изменяется память, тот же std::function именно через виртуализацию и сделан.
0
Нет, там «compile time polymorphism», через шаблоны.
0
Можете рассказать на пальцах, как там в компайл-тайм (т.е. без вирт. функций и указателей на функции) реализован вызов объектов, конкретный тип которых неизвестен коду, вызывающему () для объекта function<>?
Например для случая:
Например для случая:
// module 1
void fun()
{
}
struct str
{
void operator()() {}
};
std::function<void()> f1 = fun;
std::function<void()> f2 = str();
// module 2
f1();
f2();
0
Например, так:
call_func_when_out_of_scope h2([]() {
printf("in inner lambda\n");
});
0
Опять же. Если мы говорим о C++11 то, мне кажется, есть более простой вариант без использования рантайм-полиморфизма:
Варианты использования:
template<typename F>
class ScopeExit
{
public:
ScopeExit(const F& fn) : m_fn(fn) {;}
~ScopeExit()
{
m_fn();
}
private:
F m_fn;
};
template<typename F>
ScopeExit<F> CreateScopeExit(F fn)
{
return ScopeExit<F>(fn);
}
#define EXIT_SCOPE_CREATE_UNIQ_NAME2(line) exit_scope_guard_##line
#define EXIT_SCOPE_CREATE_UNIQ_NAME(line) EXIT_SCOPE_CREATE_UNIQ_NAME2(line)
#define EXIT_SCOPE(F) const auto &EXIT_SCOPE_CREATE_UNIQ_NAME(__LINE__) = CreateScopeExit([]{F;})
Варианты использования:
int main()
{
std::cout << "Outer scope enter" << std::endl;
EXIT_SCOPE(std::cout << "Outer scope exited" << std::endl);
{
std::cout << "Inner scope enter" << std::endl;
EXIT_SCOPE({std::cout << "Inner scope exited" << std::endl;});
std::cout << "Inner scope exit" << std::endl;
}
std::cout << "Outer scope exit" << std::endl;
return 0;
}
+5
То-же думал над такой реализацией, но хотелось обойтись без дополнительных скобочек. Хотя с точки зрения производительности ваш код быстрее и лучше поддаётся оптимизации
0
Есть, кстати, возможность доточить этот код до вариант «без дополнительных скобочек».
+1
Можно пример? А то у меня как-то нет вариантов.
0
Всё достаточно просто:
(класс ScopeExit тот же)
Ну и использование:
Но тут есть одна тонкость связанная больше со стилем кодирования. Декларирования макросов без параметров лучше всё-таки избегать. Резко увеличивается вероятность неожиданной коллизии.
class ScopeExitCreator
{
public:
template<typename Fn>
ScopeExit<Fn> operator << (const Fn &fn)
{
return ScopeExit<Fn>(fn);
}
};
#define EXIT_SCOPE_CREATE_UNIQ_NAME2(line) exit_scope_guard_##line
#define EXIT_SCOPE_CREATE_UNIQ_NAME(line) EXIT_SCOPE_CREATE_UNIQ_NAME2(line)
#define EXIT_SCOPE const auto &EXIT_SCOPE_CREATE_UNIQ_NAME(__LINE__) = ScopeExitCreator() << [&]()
(класс ScopeExit тот же)
Ну и использование:
int main()
{
std::cout << "Outer scope enter" << std::endl;
EXIT_SCOPE{std::cout << "Outer scope exited" << std::endl;};
{
std::cout << "Inner scope enter" << std::endl;
EXIT_SCOPE{std::cout << "Inner scope exited" << std::endl;};
std::cout << "Inner scope exit" << std::endl;
}
std::cout << "Outer scope exit" << std::endl;
return 0;
}
Но тут есть одна тонкость связанная больше со стилем кодирования. Декларирования макросов без параметров лучше всё-таки избегать. Резко увеличивается вероятность неожиданной коллизии.
+1
Чисто теоретический вопрос:
// 1
exit_scope guard1 = [&](){ std::cout
// 1
exit_scope guard1 = [&](){ std::cout
0
вот блин, стёрлось даже с использованием <source>
вопрос:
// 1
exit_scope guard1 = [&](){ std::cout<< «First exit scope „<<omega<<std::endl; };
// 2
exit_scope guard1 = [&](){ std::cout<< “First exit scope „<<omega<<std::endl; };
В случаях (1) и (2) будет сгенерирован разный код полностью для exit_scope или только для struct caller?
вопрос:
// 1
exit_scope guard1 = [&](){ std::cout<< «First exit scope „<<omega<<std::endl; };
// 2
exit_scope guard1 = [&](){ std::cout<< “First exit scope „<<omega<<std::endl; };
В случаях (1) и (2) будет сгенерирован разный код полностью для exit_scope или только для struct caller?
0
В Вашем коде есть один существенный изъян.
Для того, чтобы код в EXIT_SCOPE выполнялся всегда и самым последним в функции, этот макрос необходимо
писать в самом начале этой функции, и обязательно до вызова любого return или кода, который может бросить exception, иначе EXIT_SCOPE вообще не выполнится. Но в таком случае переменные функции, которые были объявлены позже будут не доступны внутри лямбды.
Для того, чтобы код в EXIT_SCOPE выполнялся всегда и самым последним в функции, этот макрос необходимо
писать в самом начале этой функции, и обязательно до вызова любого return или кода, который может бросить exception, иначе EXIT_SCOPE вообще не выполнится. Но в таком случае переменные функции, которые были объявлены позже будут не доступны внутри лямбды.
int test1()
{
std::string omega = "very big string"; // throw bad_alloc, EXIT_SCOPE не выполнится
if (omega.empty())
{
return -1; //EXIT_SCOPE не выполнится, это как минимум не очевидно
}
std::cin >> omega;
EXIT_SCOPE{ std::cout << "Second exit scope" << omega<< std::endl; };
EXIT_SCOPE{ std::cout << "Third exit scope" << omega<<std::endl; };
return 0;
}
0
Ну мне кажется, что это скорее фича, чем баг.
В этом есть логика — выражение не выполнится, покаместь оно не объявлено.
Я правда не знаю, как в этом случаи поведёт себя тот-же D
В этом есть логика — выражение не выполнится, покаместь оно не объявлено.
Я правда не знаю, как в этом случаи поведёт себя тот-же D
-1
Ну ничего-себе фитча, Ваш код может ввести в заблуждение и не освободить к примеру память, или не освободить ресурсы и этим вызвать зависание программы, либо вообще падение и порчу пользовательских данных.
Возникает большой соблазн написать EXIT_SCOPE внизу функции, т.к. это логичнее как-то, типа finaly блок перед выходом, да и в конце функции доступны все необходимые переменные. А иногда вообще нельзя будет написать такой блок вначале функции из-за недоступности каких-либо переменных, и такой блок на автомате перенесут в середину или в конец функции.
А только кто-то напишет EXIT_SCOPE в конце функции, то этот кусок кода уже не будет вызываться при любых исключительных выходах из функции (читай при любых нестандартных ситуациях, то есть при проверках кодов возврата вызываемых функций и при возникновении исключений).
По сути код, написанный в Вашем макросе вообще не будет отличатся от простого блока кода, то есть в таком случае Ваш макрос вообще ничего не добавит к логике программы, кроме путаницы.
Проектировать библиотечный код необходимо таким образом, чтобы избегать всевозможных ошибок.
А в Вашем случае макрос еще и прячет scope объект, что приводит к полному непониманию происходящего и по этому обязательно приведет к ошибкам.
Возникает большой соблазн написать EXIT_SCOPE внизу функции, т.к. это логичнее как-то, типа finaly блок перед выходом, да и в конце функции доступны все необходимые переменные. А иногда вообще нельзя будет написать такой блок вначале функции из-за недоступности каких-либо переменных, и такой блок на автомате перенесут в середину или в конец функции.
А только кто-то напишет EXIT_SCOPE в конце функции, то этот кусок кода уже не будет вызываться при любых исключительных выходах из функции (читай при любых нестандартных ситуациях, то есть при проверках кодов возврата вызываемых функций и при возникновении исключений).
По сути код, написанный в Вашем макросе вообще не будет отличатся от простого блока кода, то есть в таком случае Ваш макрос вообще ничего не добавит к логике программы, кроме путаницы.
Проектировать библиотечный код необходимо таким образом, чтобы избегать всевозможных ошибок.
А в Вашем случае макрос еще и прячет scope объект, что приводит к полному непониманию происходящего и по этому обязательно приведет к ошибкам.
-2
А как, по вашему, это должно работать, чтобы ещё и в правила и соглашения языка C++ укладываться? Пока то, что вы написали, похоже примерно на следующее: написать в конце метода main кучу вызовов библиотечной функции atexit, а потом удивляться — почему ничего не работает…
0
Я лишь хочу донести мысль о том, что использование предложенного макроса EXIT_SCOPE в таком виде, как он представлен неизбежно приведет к ошибкам и ухудшит сопровождение кода.
Представьте себе ситуацию, когда не особо выдающийся программист Вася вносит дополнительную проверку в чужой код перед этим макросом и вызывает return, либо throw.
При том ошибки, допущенные из-за использования такого подхода будут проявляться редко и будут трудно воспроизводимы.
Я не знаю как это должно работать, но я точно знаю что в таком виде это опасно использовать, особенно в крупных проектах над которыми годами трудится много поколений разработчиков, да еще и с разным уровнем опыта.
Возможно стоит ввести дополнительный объект, который нужно будет объявлять в начале функции, и именно при уничтожении этого объекта вызывать функтор, объявленный позже, но тут опять таки есть свои ньюансы…
Лично я лучше сделаю отдельный RAI объект для освобождения памяти или ресурсов, ну или воспользуюсь существующим unique_ptr, чем буду городить нечто подобное этому макросу.
Представьте себе ситуацию, когда не особо выдающийся программист Вася вносит дополнительную проверку в чужой код перед этим макросом и вызывает return, либо throw.
При том ошибки, допущенные из-за использования такого подхода будут проявляться редко и будут трудно воспроизводимы.
Я не знаю как это должно работать, но я точно знаю что в таком виде это опасно использовать, особенно в крупных проектах над которыми годами трудится много поколений разработчиков, да еще и с разным уровнем опыта.
Возможно стоит ввести дополнительный объект, который нужно будет объявлять в начале функции, и именно при уничтожении этого объекта вызывать функтор, объявленный позже, но тут опять таки есть свои ньюансы…
Лично я лучше сделаю отдельный RAI объект для освобождения памяти или ресурсов, ну или воспользуюсь существующим unique_ptr, чем буду городить нечто подобное этому макросу.
-1
Я ведь не зря привёл пример с atexit. Да и объект в начале функции — тоже не поможет. Сейчас SCOPE_EXIT подчиняется общим правилам control flow программы. Если выполнение до него не дошло — ничего не вызовется. Если дошло — то вызовется. Изменять эту логику на что-то более хитрое — вот это то как раз опасно, потому что появится сущность, которая «волшебным образом» будет работать по другим правилам. Чем проще контракт — тем проще его понять, и проще пользоваться.
0
О чем вообще можно говорить,
когда автор статьи не потрудился привести пример,
демонстрирующий работу своего макроса.
(или сам-же наступил на свои грабли?)
Давайте разберем его пример:
Этот код эквивалентен коду вообще без макроса:
Чтобы заработал макрос нужно его написать перед вызовом основного кода:
А затем приходит программист Вася и «исправляет» код:
Может быть автор потрудится предоставить более наглядный пример из жизни, где было бы задействовано что-то более реальное чем вывод на консоль?
когда автор статьи не потрудился привести пример,
демонстрирующий работу своего макроса.
(или сам-же наступил на свои грабли?)
Давайте разберем его пример:
int test1()
{
std::string omega;
std::cin >> omega;
EXIT_SCOPE{ std::cout << "Second exit scope" << omega<< std::endl; };
EXIT_SCOPE{ std::cout << "Third exit scope" << omega<<std::endl; };
return 0;
}
Этот код эквивалентен коду вообще без макроса:
int test1()
{
std::string omega;
std::cin >> omega;
std::cout << "Second exit scope" << omega<< std::endl;;
std::cout << "Third exit scope" << omega<<std::endl;;
return 0;
}
Чтобы заработал макрос нужно его написать перед вызовом основного кода:
int test1()
{
std::string omega;
EXIT_SCOPE{ std::cout << "Second exit scope" << omega<< std::endl; };
EXIT_SCOPE{ std::cout << "Third exit scope" << omega<<std::endl; };
std::cin >> omega; // can throw an exception
omega += " super puper omega"; // throw bad_alloc()
//и еще куча кода
if (omega.empty())
{
return -1;
}
return 0;
}
А затем приходит программист Вася и «исправляет» код:
int test1()
{
std::string omega;
std::cin >> omega; // can throw an exception
omega += " super puper omega"; // throw bad_alloc()
//и еще куча кода
if (omega.empty())
{
return -1;
}
std::string alpha;
std::cin >> alpha;
EXIT_SCOPE{ std::cout << "Second exit scope" << omega << alpha << std::endl; };
EXIT_SCOPE{ std::cout << "Third exit scope" << omega<< alpha << std::endl; };
return 0;
}
Может быть автор потрудится предоставить более наглядный пример из жизни, где было бы задействовано что-то более реальное чем вывод на консоль?
-1
В первом примере сделайте так:
По крайней мере GCC не будет ругаться на закрытый конструктор копирования.
exit_scope guard1([&]() {
std::cout<< "First exit scope " << omega << std::endl;
});
По крайней мере GCC не будет ругаться на закрытый конструктор копирования.
0
не знаю на сколько это чисто g++ фича, но заметил, что
SomeClass::some_func()
{
otherclass->set_callback([&this](){ ....; this->protected_method(); }
}
OtherClass::call_callback()
{
callback(); // который принят в set_callback(std::function);
}
работает без варнингов.
Фактически обращение через замыкание к закрытым методам.
SomeClass::some_func()
{
otherclass->set_callback([&this](){ ....; this->protected_method(); }
}
OtherClass::call_callback()
{
callback(); // который принят в set_callback(std::function);
}
работает без варнингов.
Фактически обращение через замыкание к закрытым методам.
0
Sign up to leave a comment.
Аналог scope(exit) на С++