在C++中实现类似于Golang风格的”defer”功能。

huangapple go评论89阅读模式
英文:

golang-style "defer" in C++

问题

我正在阅读关于Go语言的defer语句。它允许你在函数结束时指定一个操作。例如,如果你有一个文件指针或资源,你只需要在一个地方指定defer函数,而不是在每个可能的返回路径上写free/delete。

看起来C++可能最终会有类似的功能(https://stackoverflow.com/questions/32432450/what-is-standard-defer-finalizer-implementation-in-c,https://stackoverflow.com/questions/30405269/will-there-be-standardization-of-scope-guard-scope-exit-idioms)。在那之前,使用一个析构函数进行回调有没有什么未预料到的问题?看起来局部变量的析构顺序是合理的,并且它也很好地处理异常,尽管可能不能在信号上退出。

这是一个示例实现...有没有什么令人不安的地方?

#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!

英文:

I was reading about the go language's defer statement. It allows you to specify an action to take when a function has ended. For example, if you have a file pointer or resource, instead of writing free/delete with every possible return path, you just need to specify the defer function once.

It looks like an analogue might be coming to C++ eventually (https://stackoverflow.com/questions/32432450/what-is-standard-defer-finalizer-implementation-in-c, https://stackoverflow.com/questions/30405269/will-there-be-standardization-of-scope-guard-scope-exit-idioms) Until then, is there anything unforeseen about doing it with an object whose destructor makes a callback? It looks like the destructor order for local variables is sane and that it also handles exceptions well, though maybe not exiting on signals.

Here is a sample implementation... is there anything troubling about it?

#include &lt;iostream&gt;
#include &lt;functional&gt;
using namespace std;

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

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

Output: Hello, world!

答案1

得分: 28

你可以在Boost的Smart Pointer Programming Techniques中讨论这个问题:

例如,你可以这样做:

#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的参考实现:

C++核心准则也有一个准则,使用了gsl::finally函数,这里有一个实现:here

许多代码库都使用类似的解决方案,因此,这个工具有很大的需求。

相关的Stack Overflow讨论:

英文:

Boost discuss this in Smart Pointer Programming Techniques:

You can do, for example:

#include &lt;memory&gt;
#include &lt;iostream&gt;
#include &lt;functional&gt;

using namespace std;
using defer = shared_ptr&lt;void&gt;;    

int main() {
    defer _(nullptr, bind([]{ cout &lt;&lt; &quot;, World!&quot;; }));
    cout &lt;&lt; &quot;Hello&quot;;
}

Or, without bind:

#include &lt;memory&gt;
#include &lt;iostream&gt;

using namespace std;
using defer = shared_ptr&lt;void&gt;;    

int main() {
    defer _(nullptr, [](...){ cout &lt;&lt; &quot;, World!&quot;; });
    cout &lt;&lt; &quot;Hello&quot;;
}

You may also as well rollout your own small class for such, or make use of the reference implementation for N3830/P0052:

The C++ Core Guidelines also have a guideline which employs the gsl::finally function, for which there's an implementation here.

There are many codebases that employ similar solutions for this, hence,
there's a demand for this tool.

Related SO discussion:

答案2

得分: 7

这已经存在了,它被称为作用域保护(scope guard)。请参考这个精彩的演讲:https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C。它允许你轻松地创建一个在退出时被调用的任意可调用对象。这是更新的版本;它最初是在Go语言出现之前开发的。

它在一般情况下工作得很完美,但我不确定你所说的它如何处理异常。从一个必须在作用域退出时被调用的函数中抛出异常是一团糟。原因是:当抛出异常(并且没有立即捕获)时,当前作用域退出。所有的析构函数都会被执行,异常将继续传播。如果其中一个析构函数抛出异常,你该怎么办?现在你有两个活跃的异常。

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

英文:

This already exists, and it's called scope guard. See this fantastic talk: https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C. This lets you easily create an arbitrary callable to be called at exit. This is the newer version; it was developed originally long before go existed.

It works perfectly in general, but I'm not sure what you mean by it handling exceptions. Throwing exceptions from a function that has to be called at scope exit is a mess. The reason: when an exception is thrown (and not immediately caught), current scope exits. All destructors get run, and the exception will continue propagating. If one of the destructors throws, what do you do? You now have two live exceptions.

I suppose there are ways a language could try to deal with this, but it's very complex. In C++, it's very rare that a throwing destructor would be considered a good idea.

答案3

得分: -6

这在C++中已经存在,而且这是一个非常糟糕的想法,你给出的例子说明了为什么这是一个无意义的事情,我希望委员会永远不要引入它。

例如,如果你有一个文件句柄,那么写一个类来代替你完成这个操作,这样你就不必为每个使用情况都写一个defer语句,而且你可能会忘记这样做,或者写错。你只需要写一个析构函数,一次而已。然后你就可以确保在使用该类的所有情况下都是安全的。这样更安全,更容易。

英文:

This already exists in C++, and it's a tremendously bad idea and the example you gave exemplifies why it's a pointless thing to do and I hope that the Committee never introduces it.

For example, if you have a file handle, then write a class to do it for you and then you won't have to write a defer statement for every single use case, which you could easily forget to do. Or just plain get it wrong. You write one destructor, once. That's it. Then you're guaranteed for all uses of the class that it's safe. It's much safer and much easier.

huangapple
  • 本文由 发表于 2015年10月10日 13:23:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/33050620.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定