自动推导返回值和参数类型的函子

时间:2019-01-30 20:23:03

标签: c++ templates c++03 c++98

假设您有一个常见的工作流程,该工作流程通常会重复执行,但会有所不同:

  • 锁定互斥锁
  • 执行一些动作
  • 解锁互斥锁

我正在尝试建立一种可以针对任意操作自动执行此操作的机制(在C ++ 98中)。例如,以下内容:

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

可能成为:

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

或一些类似的机制。挑战在于如何对任意类型的a以及任意类型和数量的参数执行此操作。

我认为应该有一种使用模板执行此操作的方法,但不确定如何完成此操作。您可以对函子执行类似的操作,但必须提前告知函子其参数类型-我希望有一种方法可以从调用的原始函数中自动检测它们。这样,如果(当)函数的参数列表发生更改,则除了要调用的参数列表外,您无需更新其他任何内容。

这可能吗?

3 个答案:

答案 0 :(得分:5)

在现代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),甚至没有decltypeauto,因此我们需要想出另一种方法来获取返回类型。我们可以将其设置为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完成了部分工作,您可以使用它们的实用程序。

答案 1 :(得分:1)

让我从字面上举个例子:

  

例如,以下内容:

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

首先,您不应该编写类似的代码。为什么?这也不例外。如果foo抛出异常怎么办?您将错过释放互斥锁的机会,最终您的程序将永远等待永远不会释放的互斥锁。

避免这种情况的方法是使用RAII,也就是:“析构函数是您的朋友”。如果您不能使用带有std::scoped_lock的C ++ 17,则可以轻松编写自己的作用域锁,甚至可以使用模板来做到这一点:

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:我打算扩展这个答案,但是与此同时,已经有一个答案比我所能提供的更完整,所以我将它保留为暂时。

答案 2 :(得分:0)

一旦您有其他回答者建议的scoped_lock,就不再需要函子了。您可以这样编写示例:

return scoped_lock<Mutex>(myMutex),
          foo(arg1, arg2, arg3);
相关问题