【问题标题】:Simple way to execute code at the end of function scope [duplicate]在函数范围结束时执行代码的简单方法[重复]
【发布时间】:2018-05-04 19:56:29
【问题描述】:

偶尔在测试代码中我想设置/模拟一些全局变量,在测试/范围结束时我想恢复这些变量。例如:

BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
    std::string csrfSave = Http::getCsrfToken();
    ... some test code
    Http::setCsrfToken(csrfSave); // restore csrf
}

这里明显的问题是,如果some test code 在到达终点之前返回(或抛出),您将无法恢复该csrfSave 变量。所以,简单的改进是编写一些简单的结构包装器,在 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
}

还有什么更好的吗?我宁愿避免编写任何实用程序类,并且宁愿避免提升(除非它包含在下一个标准中)。 这种模式经常发生在不同的项目中(不共享代码),每次我最终编写简单的结构包装器或使用 lambda 的通用包装器时,我都希望看到其他可以就地编码的方法就像我在 shared_ptr exmpale 中展示的那样

【问题讨论】:

  • 您可以自己制作一个通用范围保护并将一个函数传递给它的构造函数。类似this question.
  • 当然 - unique_ptr。为什么需要 shared_ptr?带有自定义删除器的 unique_ptr 应该可以解决问题
  • @SeverinPappadeux is not unique_ptr 使用删除器作为模板参数?它会无缝地工作吗?
  • 是的,你需要模板参数。代码auto main() -&gt; int { int ttt = 77; auto deleter = [&amp;](void*) { std::cout &lt;&lt; "Deleter called\n"; std::cout &lt;&lt; ttt &lt;&lt; "\n"; // do something with capture here instead of print }; std::unique_ptr&lt;void, decltype(deleter)&gt; p((void*)1, deleter); std::cout &lt;&lt; "done\n"; return 0; }
  • @SeverinPappadeux 与我的 shared_ptr 示例相比显然太混乱了

标签: c++


【解决方案1】:

(稍后添加这些 cmets。抱歉刚刚粘贴了我的代码。我在工作中同时做 3 件事)

显然,您想要一些带有析构函数的对象,该析构函数在函数范围内被清理。

延迟调用是一个包含您指定的 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));
}

【讨论】:

  • @Joe 这是一个很好的通用解决方案,但我试图想出一些无需编写任何代码就可以工作的东西,类似于我的上一个示例。
  • @Pavel 这无需编写任何代码即可工作。将此答案中的代码视为库,您的最后一个示例将改为:auto restoreCsrf = defer([&amp;] { RBX::Http::setLastCsrfToken(csrfSave); });
  • @Pavel,正如贾斯汀所说。一旦你有了这个,只需要一行代码就可以实现你所需要的。
  • 我确切地知道它是什么以及它是如何工作的。我的 hacky 解决方案的工作方式几乎相同,无需编写任何实用程序代码,我主要寻找更好的东西,而“我宁愿避免编写任何实用程序类”。
  • 我回复的时候,你还没有写那句话。你在我回复后添加了它。请不要引用它,好像我忽略它一样
【解决方案2】:

显而易见的解决方案是创建一个对象,在其析构函数中进行清理,然后在适当的位置在堆栈上创建该对象。将std::function 作为参数并返回一个对象然后在其析构函数中运行该函数的函数可能是我首选的实现。这样我就可以做类似的事情

const auto guard{makeScopeGuard(...)};

然后只需将适当的 lambda 作为“...”传递,并且知道每当 guard 被销毁时 - 由于超出范围或由于异常或 其他原因,然后我的 lambda运行并执行任何需要的清理工作。

【讨论】:

  • 什么是makeScopeGuard
猜你喜欢
  • 2011-01-08
  • 2015-04-25
  • 2023-03-30
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
相关资源
最近更新 更多