Функтор, который автоматически выводит возвращаемые значения и типы параметров - PullRequest
0 голосов
/ 30 января 2019

Скажем, у вас есть общий рабочий процесс, который часто повторяется, но с некоторыми вариациями:

  • блокировка мьютекса
  • выполнение некоторых действий
  • разблокировка мьютекса

Я пытаюсь создать механизм, который может автоматически делать это для произвольных действий (в C ++ 98).Например, следующее:

myMutex.acquire();
int a = foo(arg1, arg2, arg3);
myMutex.release();
return a;

Может стать:

return doMutexProtected(myMutex, foo, arg1, arg2, arg3);

Или какой-то подобный механизм.Проблема в том, как сделать это для произвольных типов a и произвольных типов и количества аргументов.

У меня есть ощущение, что должен быть способ сделать это с помощью шаблонов, но я не уверен, какчтобы сделать это.Вы можете сделать что-то подобное с функторами, но вы должны заранее сообщить функторам их типы параметров - я надеюсь, что они будут автоматически обнаруживаться из исходной вызываемой функции.Таким образом, если (когда) список параметров функции изменяется, вам не нужно ничего обновлять, кроме списка параметров, с которым вы вызываете.

Возможно ли это?

Ответы [ 3 ]

0 голосов
/ 31 января 2019

Позвольте мне привести ваш пример буквально:

Например, следующее:

myMutex.acquire();
int a = foo(arg1, arg2, arg3);
myMutex.release();
return a;

Во-первых, вы не должны писать такой код.Зачем?Это не исключение безопасно.Что если foo выдаст исключение?Вы пропустите мьютекс, и в конечном итоге ваша программа будет ждать мьютекс, который никогда не будет выпущен.

Способ избежать этого - использовать RAII , он же: «Деструкторы - ваш друг».Если вы не можете использовать C ++ 17 с std::scoped_lock, вы можете легко написать свою собственную блокировку области действия и даже использовать шаблон для этого:

template <typename mutex_t>
struct my_scoped_lock {
     mutex_t& m;
     scoped_lock(mutex_t& m) : m(m) {m.acquire();}
     ~scoped_lock() { m.release(); }
};

Теперь вы не можете забыть освободить мьютекс:

int foo( /*...*/ ) {
    my_scoped_lock<mutex_t> lock(myMutex);
    int a = foo(arg1,arg2,arg3);
    return a;
}     

Если вы хотите узнать, как написать функцию, которую вы на самом деле запрашиваете, я отсылаю вас к другому ответу, я бы просто предложил вам пересмотреть, стоит ли это действительно усилий.

Или какой-то похожий механизм.Задача состоит в том, как сделать это для произвольных типов a и произвольных типов и чисел аргументов.

Может быть, этот "другой механизм" просто пишет функцию, как я указал выше.

PS: я собирался расширить этот ответ, но в то же время уже есть ответ более полный, чем я мог бы предоставить, поэтому я просто оставлю его в покое.

0 голосов
/ 31 января 2019

Если у вас есть scoped_lock, как предлагают другие ответы, вам больше не нужен функтор.Вы можете написать свой пример так:

return scoped_lock<Mutex>(myMutex),
          foo(arg1, arg2, arg3);
0 голосов
/ 31 января 2019

В современном C ++ (C ++ 17) функция будет выглядеть примерно так:

template <typename Mutex, typename Func, typename... Args>
decltype(auto) doMutexProtected(Mutex& mutex, Func&& func, Args&&... args)
{
    std::unique_lock lg(mutex);
    return std::forward<Func>(func)(std::forward<Args>(args)...);
}

Это блокирует мьютекс в типе RAII, так что все пути выхода освобождают мьютекс, а затем прекрасно перенаправляют функцию и ееаргументы, возвращающие точный тип func возвращает.

Теперь, поскольку вы не можете использовать современный C ++, мы должны постараться реализовать как можно больше из вышеперечисленного, и есть пара способов, которыми вы могли быподойти к проблеме.Реализация std::unique_lock довольно тривиальна.В зависимости от того, какие функции вы хотите, это может быть просто, как

template <typename Mutex>
class my_unique_lock
{
public:
    unique_lock(Mutex& mutex) : mutex(mutex) { mutex.lock(); }
    ~unique_lock() { mutex.unlock(); }
private:
    Mutex& mutex;
    unique_lock(unique_lock const&); // make it non copyable
};

Так, что покрыть 25% проблемы :).К сожалению, это была самая легкая часть.Поскольку в C ++ 98/03 нет decltype(auto), или даже decltype или auto, нам нужно найти другой способ получения возвращаемого типа.Мы могли бы сделать это void и использовать выходной параметр, что означало бы, что вам не нужно ничего указывать при вызове функции, но это означает, что вы не можете получить ссылку на то, что возвращается.Ценой необходимости указать тип возвращаемого вами значения вы могли бы иметь такую ​​функцию, как

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func func, Arg1 arg1)
{
    my_unique_lock<Mutex> lg(mutex);
    return func(arg1);
}

, и вы бы назвали ее как

T foo = doMutexProtected<T>(mutex, func, arg);
T& bar = doMutexProtected<T&>(mutex, func, arg);

, поскольку C ++ 98/03 неУ вас нет вариативных шаблонов, вам остается добавить к этому кучу перегрузок для различного количества аргументов, и вам придется решить, в какой момент достаточно аргументов, то есть:

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func func, Arg1 arg1) {...}

template <typename Ret, typename Mutex, typename Func, typename Arg1, typename Arg2>
Ret doMutexProtected(Mutex& mutex, Func func, Arg1 arg1, Arg2 arg2) {...}

template <typename Ret, typename Mutex, typename Func, typename Arg1, typename Arg2, typename Arg3>
Ret doMutexProtected(Mutex& mutex, Func func, Arg1 arg1, Arg2 arg2, Arg3 arg3) {...}
...

итогда вам приходится иметь дело со ссылками.Современная версия отлично пересылает все (ничего не копируется, если это не требуется Func).Мы не можем сделать это в C ++ 98/03, поэтому нам нужно добавить все имеющиеся ссылочные перестановки, чтобы мы не делали ненужных копий, как в первой версии.Это означает, что

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func func, Arg1 arg1)

на самом деле должно быть

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func& func, Arg1& arg1) {...}

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func const& func, Arg1& arg1)  {...}

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func& func, Arg1 const& arg1)  {...}

template <typename Ret, typename Mutex, typename Func, typename Arg1>
Ret doMutexProtected(Mutex& mutex, Func conts& func, Arg1 const& arg1)  {...}

, и это будет увеличиваться по мере добавления дополнительных параметров.

Если вы не хотите делатьвсе это, я полагаю, Boost выполнило хотя бы часть этой работы для C ++ 03, и вы можете использовать их утилиты.

...