什么是 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;
}