什么是 C++ 中的标准 defer/finalizer 实现?
What is standard defer/finalizer implementation in C++?
解释了 Golang 风格的总体思路 defer
here and here。
我想知道,STL(C++11、C++14 等)或 Boost 或其他库是否包含此类 class 的实现?所以我可以直接使用它而无需在每个新项目中重新实现它。
有一个 proposal for std::unique_resource_t
可以启用类似
的代码
auto file=make_unique_resource(::fopen(filename.c_str(),"w"),&::fclose);
对于资源,它定义了一个通用的scope_exit
,应该和defer
:
一样
// Always say goodbye before returning,
auto goodbye = make_scope_exit([&out]() ->void
{
out << "Goodbye world..." << std::endl;
});
在新标准出现之前,我使用简单的 RAII class:
struct ScopeGuard {
typedef std::function< void() > func_type;
explicit ScopeGuard(func_type _func) : func(_func), isReleased(false) {}
~ScopeGuard() {
if( !isReleased && func ) try {
func();
}catch(...) {};
}
void Forget() { isReleased=true; }
//noncopyable
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
private:
func_type func;
bool isReleased;
};
以后可以用于任何事情,例如:
FILE *fp = fopen(filename.c_str(),"w");
if(fp==NULL) throw invalid_argument();
ScopeGuard _fp_guard([&fp]() {
fclose(fp);
});
此外,您可以使用 Boost.ScopeExit 和类似的实现。
这是我的解决方案,与您在 swift 中遇到的类型类似,但我不处理任何异常(如果需要,添加起来很容易,只需使用 try/catch像 PSIAlt 解决方案中的块一样):
class Defer {
using F = std::function<void(void)>;
std::vector<F> funcs;
void add(F f) {
funcs.push_back(f);
}
public:
Defer(F f) { add(f); }
Defer() {}
Defer(const Defer& ) = delete;
Defer& operator= (const Defer& ) = delete;
void operator() (F f) { add(f); }
~Defer() {
for(;!funcs.empty();) {
funcs.back()();
funcs.pop_back();
}
}
};
由于使用了 vector,它可能看起来很笨重,但它保留了 swift 的 defer 函数以相反顺序调用的行为:
Defer defer([]{std::cout << "fourth" << std::endl;});
std::cout << "first" << std::endl;
auto s = "third";
defer([&s]{std::cout << s << std::endl;});
std::cout << "second" << std::endl;
但它比 defer 更强大一点,因为您可以在比您自己的范围低的任何范围内添加延迟调用。你只需要小心,当你使用这个时,捕获的 lambda 不会超出范围(有点缺陷,但如果你小心的话,不会很严重)。
我通常不会使用它,除非它是一次性声明。理想情况下,您会将任何资源包装在一个新的 class 周围,当它超出范围时会释放它,但如果它的顶级代码具有大量资源和错误处理,从代码可读性的角度来看 defer 确实更有意义.您不必记住一堆实际上都做同样事情的新 classes。
这样使用:
int main() {
int age = 20;
DEFER { std::cout << "age = " << age << std::endl; };
DEFER { std::cout << "I'll be first\n"; };
}
我的实现(请自行添加头文件):
class ScopeExit
{
public:
ScopeExit() = default;
template <typename F, typename... Args>
ScopeExit(F&& f, Args&&... args)
{
// Bind all args, make args list empty
auto temp = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
// Discard return type, make return type = void, conform to func_ type
func_ = [temp]() { (void)temp(); };
}
ScopeExit(ScopeExit&& r)
{
func_ = std::move(r.func_);
}
// Destructor and execute defered function
~ScopeExit()
{
if (func_)
func_();
}
private:
std::function< void ()> func_;
};
// Ugly macro, help
#define CONCAT(a, b) a##b
#define DEFER _MAKE_DEFER_HELPER_(__LINE__)
#define _MAKE_DEFER_HELPER_(line) ScopeExit CONCAT(_defer, line) = [&] ()
我在 CppCon 2014 (YouTube link) 上展示了 Go-style defer
的 header-only 实现;我称之为Auto
。恕我直言,就可教性、效率和绝对性而言,这仍然是最好的选择 fool-proofness。在使用中,它看起来像这样:
#include "auto.h"
int main(int argc, char **argv)
{
Auto(std::cout << "Goodbye world" << std::endl); // defer a single statement...
int x[4], *p = x;
Auto(
if (p != x) { // ...or a whole block's worth of control flow
delete p;
}
);
if (argc > 4) { p = new int[argc]; }
}
The implementation 看起来像这样:
#pragma once
template <class Lambda> class AtScopeExit {
Lambda& m_lambda;
public:
AtScopeExit(Lambda& action) : m_lambda(action) {}
~AtScopeExit() { m_lambda(); }
};
#define Auto_INTERNAL2(lname, aname, ...) \
auto lname = [&]() { __VA_ARGS__; }; \
AtScopeExit<decltype(lname)> aname(lname);
#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y
#define Auto_INTERNAL1(ctr, ...) \
Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \
Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)
#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)
是的,这就是整个文件:只有 15 行代码!它需要 C++11 或更新版本,并且需要你的编译器支持 __COUNTER__
(尽管你可以使用 __LINE__
作为穷人的 __COUNTER__
如果你需要移植到一些不支持的编译器支持一下)。至于效率,我从未见过 GCC 或 Clang 为 -O2
或更高级别的 Auto
的任何使用生成完美代码之外的任何东西——它是那些传说中的 "zero-cost abstractions."
The original source 也有一个 C89 版本,它通过利用一些 very GCC-specific 属性在 GCC 上工作。
此实现 zero-overhead 不同于其他一些答案,并且在语法上更好且更易于使用。它还具有零依赖性,减少了编译时间。
您可以将此代码段粘贴到您的代码库中的任何位置,它就会正常工作。
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
用法:defer { statements; };
示例:
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
bool read_entire_file(char *filename, std::uint8_t *&data_out,
std::size_t *size_out = nullptr) {
if (!filename)
return false;
auto file = std::fopen(filename, "rb");
if (!file)
return false;
defer { std::fclose(file); }; // don't need to write an RAII file wrapper.
if (std::fseek(file, 0, SEEK_END) != 0)
return false;
auto filesize = std::fpos_t{};
if (std::fgetpos(file, &filesize) != 0 || filesize < 0)
return false;
auto checked_filesize = static_cast<std::uintmax_t>(filesize);
if (checked_filesize > SIZE_MAX)
return false;
auto usable_filesize = static_cast<std::size_t>(checked_filesize);
// Even if allocation or read fails, this info is useful.
if (size_out)
*size_out = usable_filesize;
auto memory_block = new std::uint8_t[usable_filesize];
data_out = memory_block;
if (memory_block == nullptr)
return false;
std::rewind(file);
if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)
return false; // Allocation succeeded, but read failed.
return true;
}
int main(int argc, char **argv) {
if (argc < 2)
return -1;
std::uint8_t *file_data = nullptr;
std::size_t file_size = 0;
auto read_success = read_entire_file(argv[1], file_data, &file_size);
defer { delete[] file_data; }; // don't need to write an RAII string wrapper.
if (read_success) {
for (std::size_t i = 0; i < file_size; i += 1)
std::printf("%c", static_cast<char>(file_data[i]));
return 0;
} else {
return -1;
}
}
P.S.: local deferrer object 以 zz_
开头而不是 _
所以它不会在你的调试器中混淆 Locals window ,也因为从技术上讲,用户标识符不应以下划线开头。
这是我的 defer 实现,但没有 noexcept 保证,我仍然认为它不是很好的实现。
这样使用:
#include <iostream>
#include "defer.hpp"
using namespace std;
int main() {
defer []{cout << "defered" << endl;};
}
实施:
#define DEFER_CONCAT_IMPL(x, y) x##y
#define DEFER_CONCAT(x, y) DEFER_CONCAT_IMPL(x, y)
#define AUTO_DEFER_VAR DEFER_CONCAT(__defer, __LINE__)
#define defer ::__defer AUTO_DEFER_VAR; AUTO_DEFER_VAR-
class __defer {
public:
template<typename Callable>
void operator- (Callable&& callable) {
defer_ = std::forward<Callable>(callable);
}
~__defer() {
defer_();
}
private:
std::function<void(void)> defer_;
};
libgolang provides defer implementation (commit 1, 2, 3)。用法如下:
void myfunc() {
defer([]() {
printf("leaving...\n");
});
...
}
defer
本身实现为:
// defer(f) mimics `defer f()` from golang.
// NOTE contrary to Go f is called at end of current scope, not function.
#define defer(f) golang::_deferred _defer_(__COUNTER__) (f)
#define _defer_(counter) _defer_2(counter)
#define _defer_2(counter) _defer_##counter
struct _deferred {
typedef func<void()> F;
F f;
_deferred(F f) : f(f) {}
~_deferred() { f(); }
private:
_deferred(const _deferred&); // don't copy
_deferred(_deferred&&); // don't move
};
我们不应该在这里使用 std::function,因为创建 std::function 的对象相当慢。
Here 是 C++11 中使用 lambda 而不是 std::function 的实现。编译器将内联 lambda,我们将获得最佳性能。
#include <utility>
template <typename F>
struct _defer_class {
_defer_class(F&& f) : _f(std::forward<F>(f)) {}
~_defer_class() { _f(); }
typename std::remove_reference<F>::type _f;
};
template <typename F>
inline _defer_class<F> _create_defer_class(F&& f) {
return _defer_class<F>(std::forward<F>(f));
}
#define _defer_name_cat(x, n) x##n
#define _defer_name(x, n) _defer_name_cat(x, n)
#define _defer_var_name _defer_name(_defer_var_, __LINE__)
#define defer(e) \
auto _defer_var_name = _create_defer_class([&](){ e; })
我们可以这样使用:
#include "co/defer.h"
#include "co/log.h"
void f(int x, int y) {
COUT << (x + y);
}
int main(int argc, char** argv) {
defer(COUT << "hello world");
defer(COUT << "hello again");
defer(f(1, 1); f(1, 3));
return 0;
}
解释了 Golang 风格的总体思路 defer
here and here。
我想知道,STL(C++11、C++14 等)或 Boost 或其他库是否包含此类 class 的实现?所以我可以直接使用它而无需在每个新项目中重新实现它。
有一个 proposal for std::unique_resource_t
可以启用类似
auto file=make_unique_resource(::fopen(filename.c_str(),"w"),&::fclose);
对于资源,它定义了一个通用的scope_exit
,应该和defer
:
// Always say goodbye before returning,
auto goodbye = make_scope_exit([&out]() ->void
{
out << "Goodbye world..." << std::endl;
});
在新标准出现之前,我使用简单的 RAII class:
struct ScopeGuard {
typedef std::function< void() > func_type;
explicit ScopeGuard(func_type _func) : func(_func), isReleased(false) {}
~ScopeGuard() {
if( !isReleased && func ) try {
func();
}catch(...) {};
}
void Forget() { isReleased=true; }
//noncopyable
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
private:
func_type func;
bool isReleased;
};
以后可以用于任何事情,例如:
FILE *fp = fopen(filename.c_str(),"w");
if(fp==NULL) throw invalid_argument();
ScopeGuard _fp_guard([&fp]() {
fclose(fp);
});
此外,您可以使用 Boost.ScopeExit 和类似的实现。
这是我的解决方案,与您在 swift 中遇到的类型类似,但我不处理任何异常(如果需要,添加起来很容易,只需使用 try/catch像 PSIAlt 解决方案中的块一样):
class Defer {
using F = std::function<void(void)>;
std::vector<F> funcs;
void add(F f) {
funcs.push_back(f);
}
public:
Defer(F f) { add(f); }
Defer() {}
Defer(const Defer& ) = delete;
Defer& operator= (const Defer& ) = delete;
void operator() (F f) { add(f); }
~Defer() {
for(;!funcs.empty();) {
funcs.back()();
funcs.pop_back();
}
}
};
由于使用了 vector,它可能看起来很笨重,但它保留了 swift 的 defer 函数以相反顺序调用的行为:
Defer defer([]{std::cout << "fourth" << std::endl;});
std::cout << "first" << std::endl;
auto s = "third";
defer([&s]{std::cout << s << std::endl;});
std::cout << "second" << std::endl;
但它比 defer 更强大一点,因为您可以在比您自己的范围低的任何范围内添加延迟调用。你只需要小心,当你使用这个时,捕获的 lambda 不会超出范围(有点缺陷,但如果你小心的话,不会很严重)。
我通常不会使用它,除非它是一次性声明。理想情况下,您会将任何资源包装在一个新的 class 周围,当它超出范围时会释放它,但如果它的顶级代码具有大量资源和错误处理,从代码可读性的角度来看 defer 确实更有意义.您不必记住一堆实际上都做同样事情的新 classes。
这样使用:
int main() {
int age = 20;
DEFER { std::cout << "age = " << age << std::endl; };
DEFER { std::cout << "I'll be first\n"; };
}
我的实现(请自行添加头文件):
class ScopeExit
{
public:
ScopeExit() = default;
template <typename F, typename... Args>
ScopeExit(F&& f, Args&&... args)
{
// Bind all args, make args list empty
auto temp = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
// Discard return type, make return type = void, conform to func_ type
func_ = [temp]() { (void)temp(); };
}
ScopeExit(ScopeExit&& r)
{
func_ = std::move(r.func_);
}
// Destructor and execute defered function
~ScopeExit()
{
if (func_)
func_();
}
private:
std::function< void ()> func_;
};
// Ugly macro, help
#define CONCAT(a, b) a##b
#define DEFER _MAKE_DEFER_HELPER_(__LINE__)
#define _MAKE_DEFER_HELPER_(line) ScopeExit CONCAT(_defer, line) = [&] ()
我在 CppCon 2014 (YouTube link) 上展示了 Go-style defer
的 header-only 实现;我称之为Auto
。恕我直言,就可教性、效率和绝对性而言,这仍然是最好的选择 fool-proofness。在使用中,它看起来像这样:
#include "auto.h"
int main(int argc, char **argv)
{
Auto(std::cout << "Goodbye world" << std::endl); // defer a single statement...
int x[4], *p = x;
Auto(
if (p != x) { // ...or a whole block's worth of control flow
delete p;
}
);
if (argc > 4) { p = new int[argc]; }
}
The implementation 看起来像这样:
#pragma once
template <class Lambda> class AtScopeExit {
Lambda& m_lambda;
public:
AtScopeExit(Lambda& action) : m_lambda(action) {}
~AtScopeExit() { m_lambda(); }
};
#define Auto_INTERNAL2(lname, aname, ...) \
auto lname = [&]() { __VA_ARGS__; }; \
AtScopeExit<decltype(lname)> aname(lname);
#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y
#define Auto_INTERNAL1(ctr, ...) \
Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \
Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)
#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)
是的,这就是整个文件:只有 15 行代码!它需要 C++11 或更新版本,并且需要你的编译器支持 __COUNTER__
(尽管你可以使用 __LINE__
作为穷人的 __COUNTER__
如果你需要移植到一些不支持的编译器支持一下)。至于效率,我从未见过 GCC 或 Clang 为 -O2
或更高级别的 Auto
的任何使用生成完美代码之外的任何东西——它是那些传说中的 "zero-cost abstractions."
The original source 也有一个 C89 版本,它通过利用一些 very GCC-specific 属性在 GCC 上工作。
此实现 zero-overhead 不同于其他一些答案,并且在语法上更好且更易于使用。它还具有零依赖性,减少了编译时间。
您可以将此代码段粘贴到您的代码库中的任何位置,它就会正常工作。
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
用法:defer { statements; };
示例:
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
bool read_entire_file(char *filename, std::uint8_t *&data_out,
std::size_t *size_out = nullptr) {
if (!filename)
return false;
auto file = std::fopen(filename, "rb");
if (!file)
return false;
defer { std::fclose(file); }; // don't need to write an RAII file wrapper.
if (std::fseek(file, 0, SEEK_END) != 0)
return false;
auto filesize = std::fpos_t{};
if (std::fgetpos(file, &filesize) != 0 || filesize < 0)
return false;
auto checked_filesize = static_cast<std::uintmax_t>(filesize);
if (checked_filesize > SIZE_MAX)
return false;
auto usable_filesize = static_cast<std::size_t>(checked_filesize);
// Even if allocation or read fails, this info is useful.
if (size_out)
*size_out = usable_filesize;
auto memory_block = new std::uint8_t[usable_filesize];
data_out = memory_block;
if (memory_block == nullptr)
return false;
std::rewind(file);
if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)
return false; // Allocation succeeded, but read failed.
return true;
}
int main(int argc, char **argv) {
if (argc < 2)
return -1;
std::uint8_t *file_data = nullptr;
std::size_t file_size = 0;
auto read_success = read_entire_file(argv[1], file_data, &file_size);
defer { delete[] file_data; }; // don't need to write an RAII string wrapper.
if (read_success) {
for (std::size_t i = 0; i < file_size; i += 1)
std::printf("%c", static_cast<char>(file_data[i]));
return 0;
} else {
return -1;
}
}
P.S.: local deferrer object 以 zz_
开头而不是 _
所以它不会在你的调试器中混淆 Locals window ,也因为从技术上讲,用户标识符不应以下划线开头。
这是我的 defer 实现,但没有 noexcept 保证,我仍然认为它不是很好的实现。
这样使用:
#include <iostream>
#include "defer.hpp"
using namespace std;
int main() {
defer []{cout << "defered" << endl;};
}
实施:
#define DEFER_CONCAT_IMPL(x, y) x##y
#define DEFER_CONCAT(x, y) DEFER_CONCAT_IMPL(x, y)
#define AUTO_DEFER_VAR DEFER_CONCAT(__defer, __LINE__)
#define defer ::__defer AUTO_DEFER_VAR; AUTO_DEFER_VAR-
class __defer {
public:
template<typename Callable>
void operator- (Callable&& callable) {
defer_ = std::forward<Callable>(callable);
}
~__defer() {
defer_();
}
private:
std::function<void(void)> defer_;
};
libgolang provides defer implementation (commit 1, 2, 3)。用法如下:
void myfunc() {
defer([]() {
printf("leaving...\n");
});
...
}
defer
本身实现为:
// defer(f) mimics `defer f()` from golang.
// NOTE contrary to Go f is called at end of current scope, not function.
#define defer(f) golang::_deferred _defer_(__COUNTER__) (f)
#define _defer_(counter) _defer_2(counter)
#define _defer_2(counter) _defer_##counter
struct _deferred {
typedef func<void()> F;
F f;
_deferred(F f) : f(f) {}
~_deferred() { f(); }
private:
_deferred(const _deferred&); // don't copy
_deferred(_deferred&&); // don't move
};
我们不应该在这里使用 std::function,因为创建 std::function 的对象相当慢。
Here 是 C++11 中使用 lambda 而不是 std::function 的实现。编译器将内联 lambda,我们将获得最佳性能。
#include <utility>
template <typename F>
struct _defer_class {
_defer_class(F&& f) : _f(std::forward<F>(f)) {}
~_defer_class() { _f(); }
typename std::remove_reference<F>::type _f;
};
template <typename F>
inline _defer_class<F> _create_defer_class(F&& f) {
return _defer_class<F>(std::forward<F>(f));
}
#define _defer_name_cat(x, n) x##n
#define _defer_name(x, n) _defer_name_cat(x, n)
#define _defer_var_name _defer_name(_defer_var_, __LINE__)
#define defer(e) \
auto _defer_var_name = _create_defer_class([&](){ e; })
我们可以这样使用:
#include "co/defer.h"
#include "co/log.h"
void f(int x, int y) {
COUT << (x + y);
}
int main(int argc, char** argv) {
defer(COUT << "hello world");
defer(COUT << "hello again");
defer(f(1, 1); f(1, 3));
return 0;
}