什么是 C++ 中的标准延迟/终结器实现?

Golang 风格的一般思想在这里这里defer解释。

我想知道,STL(C++11,C++14,...)或者 Boost 或者其他一些库是否包含这样一个类的实现?所以我可以直接使用它,而无需在每个新项目中重新实现它。


慕神8447489
浏览 171回答 3
3回答

回首忆惘然

我defer在 CppCon 2014(YouTube 链接)上展示了 Go 风格的仅标头实现;我叫它Auto。恕我直言,就可教性、效率和绝对的万无一失而言,这仍然是最好的选择。在使用中,它看起来像这样:#include "auto.h"int main(int argc, char **argv){&nbsp; &nbsp; Auto(std::cout << "Goodbye world" << std::endl);&nbsp; // defer a single statement...&nbsp; &nbsp; int x[4], *p = x;&nbsp; &nbsp; Auto(&nbsp; &nbsp; &nbsp; &nbsp; if (p != x) {&nbsp; // ...or a whole block's worth of control flow&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete p;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; );&nbsp; &nbsp; if (argc > 4) { p = new int[argc]; }}实现如下所示:#pragma oncetemplate <class Lambda> class AtScopeExit {&nbsp; Lambda& m_lambda;public:&nbsp; AtScopeExit(Lambda& action) : m_lambda(action) {}&nbsp; ~AtScopeExit() { m_lambda(); }};#define Auto_INTERNAL2(lname, aname, ...) \&nbsp; &nbsp; auto lname = [&]() { __VA_ARGS__; }; \&nbsp; &nbsp; AtScopeExit<decltype(lname)> aname(lname);#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y#define Auto_INTERNAL1(ctr, ...) \&nbsp; &nbsp; Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)是的,这就是整个文件:只有 15 行代码!它需要 C++11 或更高版本,并且需要您的编译器支持__COUNTER__(尽管如果您需要可移植到某些不支持它的编译器,您可以将其__LINE__用作穷人的__COUNTER__)。至于效率,我从来没有见过 GCC 或 Clang 为Autoat-O2或更高级别的任何使用生成完美的代码——这是传说中的“零成本抽象”之一。原始源还有一个 C89 版本,它通过利用一些非常特定于 GCC 的属性在 GCC 上工作。

翻阅古今

与其他一些答案不同,此实现是零开销的,并且在语法上更好且更易于使用。它还具有零依赖性,从而减少了编译时间。您可以将此代码段粘贴到代码库中的任何位置,它就会正常工作。#ifndef deferstruct 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 deferstruct 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 // deferbool read_entire_file(char *filename, std::uint8_t *&data_out,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::size_t *size_out = nullptr) {&nbsp; &nbsp; if (!filename)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; auto file = std::fopen(filename, "rb");&nbsp; &nbsp; if (!file)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; defer { std::fclose(file); }; // don't need to write an RAII file wrapper.&nbsp; &nbsp; if (std::fseek(file, 0, SEEK_END) != 0)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; auto filesize = std::fpos_t{};&nbsp; &nbsp; if (std::fgetpos(file, &filesize) != 0 || filesize < 0)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; auto checked_filesize = static_cast<std::uintmax_t>(filesize);&nbsp; &nbsp; if (checked_filesize > SIZE_MAX)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; auto usable_filesize = static_cast<std::size_t>(checked_filesize);&nbsp; &nbsp; // Even if allocation or read fails, this info is useful.&nbsp; &nbsp; if (size_out)&nbsp; &nbsp; &nbsp; &nbsp; *size_out = usable_filesize;&nbsp; &nbsp; auto memory_block = new std::uint8_t[usable_filesize];&nbsp; &nbsp; data_out = memory_block;&nbsp; &nbsp; if (memory_block == nullptr)&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; std::rewind(file);&nbsp; &nbsp; if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)&nbsp; &nbsp; &nbsp; &nbsp; return false; // Allocation succeeded, but read failed.&nbsp; &nbsp; return true;}int main(int argc, char **argv) {&nbsp; &nbsp; if (argc < 2)&nbsp; &nbsp; &nbsp; &nbsp; return -1;&nbsp; &nbsp; std::uint8_t *file_data = nullptr;&nbsp; &nbsp; std::size_t file_size = 0;&nbsp; &nbsp; auto read_success = read_entire_file(argv[1], file_data, &file_size);&nbsp; &nbsp; defer { delete[] file_data; }; // don't need to write an RAII string wrapper.&nbsp; &nbsp; if (read_success) {&nbsp; &nbsp; &nbsp; &nbsp; for (std::size_t i = 0; i < file_size; i += 1)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::printf("%c", static_cast<char>(file_data[i]));&nbsp; &nbsp; &nbsp; &nbsp; return 0;&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; return -1;&nbsp; &nbsp; }}PS:本地延迟对象以zz_和开头,而不是_这样它不会使调试器中的本地窗口混乱,而且因为用户标识符在技术上不应该以下划线开头。

温温酱

有一个提案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;});它将被考虑在 Post-C++17 标准中采用。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go