【问题标题】:golang-style "defer" in C++ [duplicate]C ++中的golang风格的“延迟” [重复]
【发布时间】:2015-10-10 05:23:17
【问题描述】:

我正在阅读有关 go 语言的 defer 声明。它允许您指定函数结束时要执行的操作。例如,如果你有一个文件指针或资源,而不是用每个可能的返回路径写 free/delete,你只需要指定一次 defer 函数。

看起来类似的东西最终可能会出现在 C++ 中(What is standard defer/finalizer implementation in C++?Will there be standardization of scope guard/scope exit idioms?)在此之前,使用析构函数进行回调的对象有什么不可预见的事情吗?它看起来像 destructor order for local variables is sane 并且它也能很好地处理异常,尽管可能不会退出信号。

这是一个示例实现...有什么问题吗?

#include <iostream>
#include <functional>
using namespace std;

class FrameExitTask {
    std::function<void()> func_;
public:
    FrameExitTask(std::function<void()> func) :
    func_(func) {
    }
    ~FrameExitTask() {
        func_();
    }
    FrameExitTask& operator=(const FrameExitTask&) = delete;
    FrameExitTask(const FrameExitTask&) = delete;
};

int main() {
    FrameExitTask outer_task([](){cout << "world!";});
    FrameExitTask inner_task([](){cout << "Hello, ";});
    if (1+1 == 2)
        return -1;
    FrameExitTask skipped_task([](){cout << "Blam";});
}

输出:Hello, world!

【问题讨论】:

  • 这可能更适合CodeReview。请注意,已经有很多类似 ScopeGuard 的类的实现,那么为什么要重新发明轮子呢?我知道的一些实现有各种好处或专用工具,我更喜欢你的版本(例如,没有类型擦除)。
  • 这个问题是每个类都应该已经完成​​了你在它们的析构函数中所做的事情,因此 C++ 中的用例比 Go 中的用例少得多。如果您正在寻找现有的实现,请检查 boost.ScopeExit。我认为 Facebook 的 Folly 也有一个。
  • 你最好标记析构函数noexcept。如果您使用FrameExitTask 的函数正常返回,您的finally 处理程序中的异常可能会起作用。如果函数由于其他异常而退出,那么您的处理程序的第二个异常将导致问题。
  • FrameExitTask 的析构函数隐式为 noexcept。也就是说,如果func_() 抛出异常,你的程序将被终止。
  • functional-programming标签有什么关系?

标签: c++ c++11 go functional-programming destructor


【解决方案1】:

Boost 在智能指针编程技术中对此进行了讨论:

你可以这样做,例如:

#include <memory>
#include <iostream>
#include <functional>

using namespace std;
using defer = shared_ptr<void>;    

int main() {
    defer _(nullptr, bind([]{ cout << ", World!"; }));
    cout << "Hello";
}

或者,没有bind

#include <memory>
#include <iostream>

using namespace std;
using defer = shared_ptr<void>;    

int main() {
    defer _(nullptr, [](...){ cout << ", World!"; });
    cout << "Hello";
}

您也可以为此推出自己的小类,或使用 N3830/P0052 的参考实现:

The C++ Core Guidelines 还有have a guideline 使用gsl::finally 函数,有一个实现here

有许多代码库为此采用了类似的解决方案,因此, 这个工具有需求。

相关SO讨论:

【讨论】:

    【解决方案2】:

    这已经存在,它被称为范围保护。看这个精彩的演讲:https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C。这使您可以轻松创建要在退出时调用的任意可调用对象。这是较新的版本;它是在 go 存在之前很久就开发的。

    一般来说它工作得很好,但我不确定你所说的处理异常是什么意思。从必须在范围退出时调用的函数中抛出异常是一团糟。原因:当抛出异常(而不是立即捕获)时,当前范围退出。所有析构函数都运行,异常将继续传播。如果其中一个析构函数抛出,你会怎么做?您现在有两个实时异常。

    我想有一种语言可以尝试处理这个问题,但它非常复杂。在 C++ 中,抛出析构函数很少被认为是一个好主意。

    【讨论】:

    • 我的意思是当在函数中抛出异常时它可以正常工作,而不是在回调中。了解 ScopeGuard 非常有帮助。谢谢!
    【解决方案3】:

    这在 C++ 中已经存在,这是一个非常糟糕的主意,您提供的示例说明了为什么这样做毫无意义,我希望委员会永远不要引入它。

    例如,如果您有一个文件句柄,然后编写一个类来为您完成它,那么您就不必为每个用例编写一个延迟语句,您很容易忘记这样做。或者只是简单地弄错了。你写一个析构函数,一次。而已。然后,您可以保证该类的所有用途都是安全的。它更安全、更容易。

    【讨论】:

    • IMO 没有通用的规则或工具,能够解决任何问题。我的意思是,在使用 raii 包装器谈论您的建议时,有些情况下您根本不想让每个临时获取/发布的包装器都使您的代码库变得混乱。 scope_guard + lambda 提供了一个很好的选择。
    • 看来你的希望终于不会实现了:) github.com/jensmaurer/papers/issues/278
    猜你喜欢
    • 1970-01-01
    • 2016-12-21
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 2017-04-03
    相关资源
    最近更新 更多