Я хочу реализовать finally
блок в моей программе на C ++, и язык, безусловно, имеет инструменты, чтобы сделать это, если не нативное средство. Мне было интересно, что лучший способ сделать это?
Эта простая реализация, кажется, на 100% безопасна.
template< typename t >
class sentry {
t o;
public:
sentry( t in_o ) : o( std::move( in_o ) ) {}
sentry( sentry && ) = delete;
sentry( sentry const & ) = delete;
~ sentry() noexcept {
static_assert( noexcept( o() ),
"Please check that the finally block cannot throw, ""and mark the lambda as noexcept." );
o();
}
};
template< typename t >
sentry< t > finally( t o ) { return { std::move( o ) }; }
noexcept
важно, потому что вы не хотите вызывать исключение, когда ваша функция уже завершает работу из-за исключения. (Это приводит к немедленному завершению.) C ++ не проверяет, что лямбда действительно ничего не может выбросить; Вы вручную проверяете и отмечаете это noexcept
, (Увидеть ниже.)
Фабричная функция необходима, потому что иначе нет способа получить тип, зависящий от лямбды.
Конструкторы копирования и перемещения должны быть удалены, поскольку они могут быть использованы для неявной генерации временного объекта, который будет реализовывать другой часовой механизм, который вызовет блок преждевременно после уничтожения. Но оператор присваивания по умолчанию остается без изменений, потому что если у вас уже есть два часовых, которые делают разные вещи, можно назначить их. (Несколько теоретически, но что угодно.)
Было бы хорошо, если бы конструктор explicit
, но это, кажется, исключает инициализацию возвращаемого значения на месте. Поскольку класс не является подвижным, объект, находящийся в области видимости вызывающего, должен быть инициализирован непосредственно выражением в return
заявление.
Чтобы использовать, просто определите охрану как это:
auto && working_state_guard = finally( [&]() noexcept {
reset_working_state();
} );
Важно связать со ссылкой, потому что объявление реального объекта в вызывающей области потребовало бы инициализации перемещения этого объекта из возвращаемого значения функции.
Около версии 4.7, g++ -Wall
даст предупреждение, что охранник не используется. Независимо от того, кодируете ли вы это или нет, вы можете добавить немного безопасности и документации в конце функции с идиомой:
static_cast< void >( working_state_guard );
Это позволяет читателю узнать о выполнении кода с самого начала области и может служить напоминанием для двойной проверки при копировании кода.
int main() {
auto && guard = finally( []() noexcept {
try {
std::cout << "Goodbye!\n";
} catch ( ... ) {
// Throwing an exception from here would be worse than *anything*.
}
} );
std::cin.exceptions( std::ios::failbit );
try {
float age;
std::cout << "How old are you?\n";
std::cin >> age;
std::cout << "You are " << age << " years (or whatever) old\n";
} catch ( std::ios::failure & ) {
std::cout << "Sorry, didn't understand that.\n";
throw;
}
static_cast< void >( guard );
}
Это производит вывод как
$ ./sentry
How old are you?
3
You are 3 years (or whatever) old.
Goodbye!
$ ./sentry
How old are you?
four
Sorry, didn't understand that.
Goodbye!
terminate called after throwing an instance of 'std::ios_base::failure'
what(): basic_ios::clear
Abort trap: 6
Глядя на некоторые из «предыдущих попыток», я вижу транзакционный commit()
метод. Я не думаю, что это относится к реализации блока ScopeGuard / finally. Реализация протокола является обязанностью содержащегося функтора, поэтому правильное разделение труда будет заключаться в инкапсуляции в него логического флага, например путем захвата bool
локальный флаг и переключение флага, когда транзакция завершена.
Аналогично, попытка отменить действие путем переназначения самого функтора — просто грязный подход. Обычно предпочитают дополнительный случай в существующем протоколе, чтобы придумывать новый протокол вокруг старого.
Альтернативное решение с использованием std :: function.
Не требуется заводская функция. Нет шаблонов для каждого использования (лучший размер ?!).
Нет std :: переместить и && вещи нужны, авто не нужно;)
class finally
{
std::function<void()> m_finalizer;
finally() = delete;
public:
finally( const finally& other ) = delete;
finally( std::function<void()> finalizer )
: m_finalizer(finalizer)
{
}
~finally()
{
std::cout << "invoking finalizer code" << std::endl;
if( m_finalizer )
m_finalizer();
}
};
Использование:
int main( int argc, char * argv[] )
{
bool something = false;
try
{
try
{
std::cout << "starting" << std::endl;
finally final([&something]() { something = true; });
std::cout << "throwing" << std::endl;
throw std::runtime_error("boom");
}
catch(std::exception & ex )
{
std::cout << "inner catch" << std::endl;
throw;
}
}
catch( std::exception & ex )
{
std::cout << "outer catch" << std::endl;
if( something )
{
std::cout << "works!" << std::endl;
}
else
{
std::cout << "NOT working!" << std::endl;
}
}
std::cout << "exiting" << std::endl;
return 0;
}
Выход:
начало
бросание
вызов кода финализатора
внутренний улов
внешний улов
работает!
выход