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

What is standard defer/finalizer implementation in C++?

本文关键字:实现 是什么 标准 延迟 c++      更新时间:2023-10-16

在这里和这里解释了Golang-style defer的总体思想。

我想知道,STL (c++ 11, c++ 14,…)或Boost或其他一些库包含这样一个类的实现吗?所以我可以直接使用它而不用在每个新项目中重新实现它

与其他答案不同,这个实现的开销为零,而且在语法上更好,更易于使用。它也没有依赖,减少了编译时间。

你可以把这段代码粘贴到你的代码库的任何地方,它就会正常工作。

#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;
    }
}

注::本地延迟对象以zz_开始,而不是_,所以它不会使调试器中的Locals窗口混乱,也因为用户标识符在技术上不应该以下划线开头。

有一个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;
});

将考虑在后c++ 17标准中可能采用

我在CppCon 2014 (YouTube链接)上展示了go风格defer的仅标头实现;我叫它Auto。在可教性、效率和绝对防傻瓜性方面,这仍然是目前最好的选择。在使用中,它看起来像这样:

#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]; }
}

实现如下:

#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时生成的代码不是完美的——这是传说中的"零成本抽象"之一。

原始源代码还有一个C89版本,它通过利用一些非常特定于GCC的属性在GCC上工作。

这是我的解决方案,它类似于您在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不会超出作用域(有点缺陷,但如果你小心的话,不是一个严重的缺陷)。

我通常不会使用这个,除非它是一个一次性语句。理想情况下,您应该将任何资源包装在一个新类周围,当它超出作用域时释放它,但是如果它的顶级代码包含大量资源和错误处理,那么从代码可读性的角度来看,defer确实更有意义。你不需要记住一堆做同样事情的新类

libgolang提供defer实现(提交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
};

在新标准到来之前,我使用简单的RAII类:

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和类似的实现

这是我的延迟实现,但没有除了保证,我仍然认为它不是很好的实现。

像这样使用:

#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_;
};

我们不应该在这里使用std::function,因为创建std::function的对象相当慢。

下面是c++ 11中使用lambda代替std::函数的实现。编译器将内联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;
}

如下所示:

int main()   {
    int  age = 20;
    DEFER { std::cout << "age = " << age << std::endl; };
    DEFER { std::cout << "I'll be firstn"; };
}

我的实现(请自己添加头文件):

 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) = [&] ()