在测试代码中偶尔我想设置/模拟一些全局变量,在测试/范围结束时我想恢复这些变量。例如:
BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
std::string csrfSave = Http::getCsrfToken();
... some test code
Http::setCsrfToken(csrfSave); // restore csrf
}
这里显而易见的问题是,如果some test code
在到达终点之前返回(或抛出),则无法恢复该csrfSave
变量。因此,简单的改进是编写一些简单的struct wrapper,它在dtor中自动恢复值:
struct CsrfSave
{
std::string csrfSave;
CsrfSave()
{
csrfSave = Http::getCsrfToken();
}
~CsrfSave()
{
Http::setCsrfToken(csrfSave);
}
};
BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
CsrfSave csrfSave;
... some test code
// dtor of CsrfSave restores csrf
}
这解决了一般问题,但是,对于您需要编写大量样板代码的每个函数。因此,问题是:可以使用什么是最短和最简单的方法来实现相同的目标,同时最小化并可能避免所有样板。
我使用的一种方法,我对它不太满意:
BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
std::string csrfSave = RBX::Http::getLastCsrfToken();
shared_ptr<void> csrfSaveScope(NULL, [&](void*) {
RBX::Http::setLastCsrfToken(csrfSave);
});
... some test code
}
有什么更好的吗?我更愿意避免编写任何实用程序类,并且宁愿避免使用boost(除非它包含在下一个std中的内容)。 这种模式经常发生在不同的项目中(不共享代码),每次我最终编写简单的struct wrapper或者使用lambda的通用包装器,我希望看到其他可以就地编码的方法就像我在我的shared_ptr exmpale中展示的那样
答案 0 :(得分:4)
(稍后添加这些评论。只是粘贴我的代码道歉。我在工作中一次做三件事)
显然,你想要一个带有析构函数的对象,它在函数范围内得到清理。
延迟调用是一个包含您指定的lambda的模板。由于它只能通过移动来获取给定的lambda,因此您拥有所有权语义。唯一剩下的就是将其中一个作为右值引用返回。这是defer()函数的工作。它接收你的lambda并在延期调用中返回它。您将该对象保存在局部变量中,然后C ++负责其余部分。
这实际上是真正帮助我“首先”移动语义的东西。我承认这种技术不是原创的
auto cleanup = defer([] { /* call me at end of scope */ );
请注意,此技术不是原创技术。
// =============================================================================
// deferred_call:
// --------------
// This struct enables us to implement deferred function calls simply in
// the defer() function below. It forces a given function to automatically
// be called at the end of scope using move-only semantics. Most
// commonly, the given function will be a lambda but that is not required.
// See the defer() function (below) for more on this
// =============================================================================
template <typename FUNC>
struct deferred_call
{
// Disallow assignment and copy
deferred_call(const deferred_call& that) = delete;
deferred_call& operator=(const deferred_call& that) = delete;
// Pass in a lambda
deferred_call(FUNC&& f)
: m_func(std::forward<FUNC>(f)), m_bOwner(true)
{
}
// Move constructor, since we disallow the copy
deferred_call(deferred_call&& that)
: m_func(std::move(that.m_func)), m_bOwner(that.m_bOwner)
{
that.m_bOwner = false;
}
// Destructor forces deferred call to be executed
~deferred_call()
{
execute();
}
// Prevent the deferred call from ever being invoked
bool cancel()
{
bool bWasOwner = m_bOwner;
m_bOwner = false;
return bWasOwner;
}
// Cause the deferred call to be invoked NOW
bool execute()
{
const auto bWasOwner = m_bOwner;
if (m_bOwner)
{
m_bOwner = false;
m_func();
}
return bWasOwner;
}
private:
FUNC m_func;
bool m_bOwner;
};
// -----------------------------------------------------------------------------
// defer: Generic, deferred function calls
// ----------------------------------------
// This function template the user the ability to easily set up any
// arbitrary function to be called *automatically* at the end of
// the current scope, even if return is called or an exception is
// thrown. This is sort of a fire-and-forget. Saves you from having
// to repeat the same code over and over or from having to add
// exception blocks just to be sure that the given function is called.
//
// If you wish, you may cancel the deferred call as well as force it
// to be executed BEFORE the end of scope.
//
// Example:
// void Foo()
// {
// auto callOnException = defer([]{ SomeGlobalFunction(); });
// auto callNoMatterWhat = defer([pObj](pObj->SomeMemberFunction(); });
//
// // Do dangerous stuff that might throw an exception ...
//
// ...
// ... blah blah blah
// ...
//
// // Done with dangerous code. We can now...
// // a) cancel either of the above calls (i.e. call cancel()) OR
// // b) force them to be executed (i.e. call execute()) OR
// // c) do nothing and they'll be executed at end of scope.
//
// callOnException.cancel(); // no exception, prevent this from happening
//
// // End of scope, If we had not canceled or executed the two
// // above objects, they'd both be executed now.
// }
// -----------------------------------------------------------------------------
template <typename F>
deferred_call<F> defer(F&& f)
{
return deferred_call<F>(std::forward<F>(f));
}
答案 1 :(得分:3)
显而易见的解决方案是创建一个对象,在其析构函数中执行清理,然后在适当的位置在堆栈上创建。一个以std::function
为参数并返回一个对象然后在其析构函数中运行该函数的函数可能是我首选的实现。所以我可以做一些像
const auto guard{makeScopeGuard(...)};
然后只需将一个合适的lambda传递给“...”并知道每当guard
被销毁时 - 由于超出范围或由于异常或任何,我的lambda运行并且需要进行任何清理。