Ловкое и комфортное использование виртуальных функций для асинхронного выполнения в C++
Для начала, чтобы объяснить, что я хочу, приведу пример классического использования виртуальной функции, а потом плавно перейду к вопросу. Итак, имеем абстрактный класс:
class Action
{
public:
Action();
virtual ~Action() = default;
QVariant act(const QList<QVariant> &args) const;
virtual bool isEnough(const QList<QVariant> &args) const = 0;
//TODO: virtual constraints();
protected:
virtual QVariant innerAct(const QList<QVariant> &args) const = 0;
};
В классе имеется функция act, описываемая, допустим, следующим образом:
QVariant Action::act(const QList<QVariant> &args) const
{
if (!isEnough(args)) {
throw "ARG_COUNT_ERROR"; //TODO: Exception class
}
//TODO: check for constraints
return innerAct(args);
}
Теперь, чтобы создать новое Действие (Action), достаточно унаследовать новый класс от класса Action и переписать ему innerAct, например:
class Inc : public Action
{
public:
Inc();
// Action interface
public:
// int argCount() const final;
bool isEnough(const QList<QVariant> &args) const final;
protected:
QVariant innerAct(const QList<QVariant> &args) const final {
return args.at(0).toInt() + 1;
}
};
Теперь достаточно будет вызвать act для любого объекта класса Action, не уточняя каким именно Действием он является, например:
QVariant Command::result(const QVariant &arg)
{
if (_result.isValid()) { //TODO: if (isPure())
return _result;
}
auto act = action();
_args.append(arg);
if (act->isEnough(_args)) {
return act->act(_args);
}
//copy self and return
_result = QVariant::fromValue(copy<Term>().nodeRef());//carring
return _result;
}
Замечательно!
Теперь, я хочу создать класс, нападобие Action, но который выполняет свое действие асинхронно (переходим к вопросу). Это может быть непосредственно создание потока (thread) или вызов функций из других асинхронных библиотек - коннект с сервером, получение удаленной информации по сети, ожидание действий пользователя, показаний датчиков). После окончания вычислений производный класс должен испустить сигнал complete, подразумевая, что его поймает пользователь этого класса. Итак, пишем асинхронный аналог нашего Action:
//Async Action
class Task : public QObject, public Reward::Node
{
Q_OBJECT
public:
Task();
virtual bool isEnough(const QList<QVariant> &args) const = 0;
void act(const QList<QVariant> &args) const;
protected:
//должна испустить complete() или соединяться, например, с потоком или с окончание выполнения асинхронной функции (?)
virtual void innerAct(const QList<QVariant> &args) const = 0;
signals:
void complete(const QVariant result);
//TODO: void error(Error err);?
};
Неудобство в том, что в унаследованным от Task классе нужно "не забыть" вызвать emit complete или что-то вроде connect(_сonnection, received, this, complete). Нет ли более ловкого способа (кроме как подробно комментировать функцию) выполнить такую задачу, так, чтобы в производном классе нужно было просто переписать функцию, возвращающую результат или использовать какой-нибудь хитроумный прием или шаблон пректирования?
Простейший пример использования:
Task *doSomething = new DoSomethingTask();
AsyncCommand *command = new AsyncCommand();
command->setTask(doSomething);
connect(doSomething, &Task::complete, command, &AsyncCommand::onTaskComplete);
Другой пример (подразумеваемого) использования:
//аналог Command::result
void AsyncCommand::result(const QVariant &arg)
{
if (_result.isValid()) { //TODO: if (isPure())
return;
}
auto tsk = task();
_args.append(arg);
if (tsk->isEnough(_args)) {
connect(tsk, &Task::complete, this, &AsynCommand::onTaskComplete);
tsk->act(_args);
return;
}
//copy self and return
_result = QVariant::fromValue(copy<Term>().nodeRef());//carring
return;
}
void AsynCommand::onTaskComplete(const QVariant &result)
{
_result = result;
//что-нибудь еще
}