假设您有一个常见的工作流程,该工作流程通常会重复执行,但会有所不同:
我正在尝试建立一种可以针对任意操作自动执行此操作的机制(在C ++ 98中)。例如,以下内容:
myMutex.acquire();
int a = foo(arg1, arg2, arg3);
myMutex.release();
return a;
可能成为:
return doMutexProtected(myMutex, foo, arg1, arg2, arg3);
或一些类似的机制。挑战在于如何对任意类型的a
以及任意类型和数量的参数执行此操作。
我认为应该有一种使用模板执行此操作的方法,但不确定如何完成此操作。您可以对函子执行类似的操作,但必须提前告知函子其参数类型-我希望有一种方法可以从调用的原始函数中自动检测它们。这样,如果(当)函数的参数列表发生更改,则除了要调用的参数列表外,您无需更新其他任何内容。
这可能吗?
答案 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)
,甚至没有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完成了部分工作,您可以使用它们的实用程序。
答案 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);