Компании-разработчики, как правило, не особо спешат переходить на новый Си++. Главным образом из-за поддержки его компиляторами, а точнее ее полного или частичного отсутствия. Недавно я решил узнать, что же есть новенького в плане поддержки C++11 компилятором GCC, и понял, что пора начинать. Благо, у нас в Ivideon лояльно относятся к новым технологиям и дают пробовать что-то новое.
Начал, конечно же, с самого вкусного — с лямбда-выражений! И с потоков.
Хочу представить читателю небольшую зарисовку на тему того, как лямбды могут помочь сделать код более читаемым и лаконичным. Такой код можно использовать всюду, где надо вызывать периодически какую-нибудь функцию, при этом передавая ей время, прошедшее с предыдущего вызова — например, в анимациях.
Заголовок:
#ifndef ANIMATION_ENGINE_H
#define ANIMATION_ENGINE_H
#include <functional>
namespace animation {
// Actor function has one parameter: time delta since previous frame
// It should return true if animation has finished,
// or false if it should go on
typedef std::function<bool(float)> actor_func;
// Handler is called after the animation is complete
typedef std::function<void(void)> handler_func;
const handler_func doNothing = [] {};
void start(unsigned intervalMs, actor_func, handler_func = doNothing);
} // end of namespace animation
#endif // ANIMATION_ENGINE_H
Интерфейс простой: в start() мы передаём интервал вызовов, функцию и необязательный обработчик завершения.
И реализация:
#include "animation_engine.h"
#include <thread>
namespace animation {
void start(unsigned intervalMs, actor_func actor, handler_func handler)
{
std::thread t([=]() {
auto timeStart = std::chrono::system_clock::now();
float lastInterval = 0;
while(1) {
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
float timeDelta = std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::system_clock::now() - timeStart).count();
if (actor(timeDelta - lastInterval)) {
handler();
break;
}
lastInterval = timeDelta;
}
});
t.detach();
}
} // end of namespace animation
Вот здесь уже интереснее: на основе функций, которые передали аргументами, и интервала, строится новая функция (замыкание), куда переданные аргументы копируются ([=] означает захват по значению всех доступных переменных). Дальше эта новая функция запускается в отдельном потоке, где она в бесконечном цикле гоняет нашу несчастную функцию actor, покуда она не вернет true. Ну, или приложение не закроется. Далее start() завершается, оставляя анимацию крутиться в своём потоке.
Ниже пример использования. Допустим, мы пишем игрушку наподобие пинг-понга или арканоида, и нам нужно привести в действие шарик. Вот как можно это реализовать:
animation::start (30,
[=] (float dt) {
this->adjustBallDirection(dt); // bouncing, maybe gravitation
this->updateBallPosition(dt);
return (this->ballMissed()) // if player missed the ball -> stop
},
[this] {
resetBall();
}
);
Компилировать это всё, как и водится, надо с ключом -std=c++0x или -std=c++0x. Кроме того, на этапе линковки нужно передать ключ -pthread. Если вы, как и я, работаете с QMake, просто добавьте эти две строчки в .pro-файл:
CONFIG += c++11
QMAKE_LFLAGS += -pthread
Возможное улучшение №1: для реальных задач неплохо бы улучшить расчет интервала между вызовами. Для этого следует учитывать время, пока отрабатывает функция-актор. А то сейчас выходит, что интервал становится чуть больше. Но я не стал заморачиваться на этот раз с этим: просто не об этом заметка.
Возможное улучшение №2: можно ввести дополнительный функционал, который бы позволил прерывать анимации. Сделать это несложно (да хотя бы введением флага, который бы все потоки с анимациями проверяли при работе), но, опять же, код примера распух бы от этого.
Вполне вероятно, что вы знаете более лаконичный способ решения подобной задачи — будет круто, если вы изложите его в комментариях. Или вы знаете более интересный кейс, где лямбды упростили бы жизнь… в общем, вы знаете, что делать.
Спасибо за внимание!
Автор: vsabadazh