【发布时间】:2017-04-14 12:55:16
【问题描述】:
对于许多 RAII "guard" 类,将其实例化为匿名变量根本没有意义:
{
std::lock_guard<std::mutex>{some_mutex};
// Does not protect the scope!
// The unnamed instance is immediately destroyed.
}
{
scope_guard{[]{ cleanup(); }};
// `cleanup()` is executed immediately!
// The unnamed instance is immediately destroyed.
}
来自this article:
C++ 中的匿名变量具有“表达式范围”,这意味着它们在创建它们的表达式结束时被销毁。
有什么方法可以阻止用户在没有名字的情况下实例化它们? (“阻止”可能太强了——“让它变得非常困难”也是可以接受的)。 em>
我可以想到两种可能的解决方法,但它们在使用类时会引入语法开销:
-
在
detail命名空间中隐藏类并提供一个宏。namespace detail { class my_guard { /* ... */ }; }; #define SOME_LIB_MY_GUARD(...) \ detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}这可行,但 hackish。
-
只允许用户通过高阶函数使用守卫。
template <typename TArgTuple, typename TF> decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f) { make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs)); f(); }用法:
with_guard(std::forward_as_tuple(some_mutex), [&] { // ... });当保护类的初始化具有“流利”语法时,此解决方法不起作用:
{ auto _ = guard_creator() .some_setting(1) .some_setting(2) .create(); }
还有更好的选择吗?我可以使用 C++17 功能。
【问题讨论】:
-
我认为你问错了方向。 C++ 开发人员应该理解为什么
lock_guard存在,以及为什么只要它保护的范围存在,它就应该保持存在。试图通过 API 或内部实现来强制执行这一点是多余的.. -
@Danh:另一个问题的答案是我的“宏观”解决方法。如果我更明显地表明我正在寻找基于非宏观的解决方案,您会考虑重新提出这个问题吗? @David:对于
lock_guard的例子,我同意。不过,我正在使用“流利的”语法进行一些非分配异步链生成,在这种情况下,拥有匿名链是很自然的。但是你仍然需要一个名字,否则链的存储很快就会死掉。它不像lock_guard那么明显,我想防止这个错误。 -
@VittorioRomeo 这个问题还有其他答案。而且,恕我直言,如果我们可以将所有关于此的讨论集中在一个地方,那就更好了。不管怎样,this answer 看起来很有趣
-
@VittorioRomeo 所以基本上,您使用的是堆栈分配版本的
std::async+future::then? -
“被实例化为匿名变量根本没有意义”,除非结果绑定到引用并延长生命周期。这是在 C++17 之前编写
make_lock_guard的唯一方法。一种可能性是强制使用make_函数,然后将其标记为[[nodiscard]]。