代码之家  ›  专栏  ›  技术社区  ›  Aleksander Alekseev

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

  •  11
  • Aleksander Alekseev  · 技术社区  · 10 年前

    Golang风格的总体构想 defer 已解释 here here .

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

    9 回复  |  直到 9 年前
        1
  •  20
  •   pmttavara LoveToCode    8 年前

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

    您可以将此代码段粘贴到代码库中的任何位置,它就会正常工作。

    #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.:本地延迟器对象以 zz_ 而不是 _ 因此它不会扰乱调试器中的Locals窗口,而且从技术上讲,用户标识符不应该以下划线开头。

        2
  •  10
  •   pmttavara LoveToCode    8 年前

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

    It will be considered for likely adoption in the Post-C++17 standard.

        3
  •  6
  •   Community Mohan Dere    9 年前

    我展示了Go风格的仅头实现 defer 2014年CppCon( YouTube link ); 我叫它 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]; }
    }
    

    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__ 作为一个穷人的 __计数器__ 如果您需要移植到不支持它的编译器)。至于效率,我从未见过GCC或Clang为任何使用 汽车 -O2 或者更高,这是传说中的“零成本抽象”之一

    The original source 还有一个C89版本,通过利用一些 非常 GCC特定属性。

        4
  •  2
  •   austere    10 年前

    这是我的解决方案,类似于您在swift中遇到的类型,但我不处理任何异常(如果需要,可以很容易地添加,只需使用PSIAlt解决方案中的try/catch块):

    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确实更有意义。你不必记住一堆新的类,它们都做同样的事情。

        5
  •  0
  •   Galimov Albert    10 年前

    在新标准出台之前,我使用简单的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和类似的实现。

        6
  •  0
  •   phonzia    8 年前

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

    使用方式如下:

    #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_;
    };
    
        7
  •  0
  •   kirr    6 年前

    libgolang 提供 推迟 实施( 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
    };
    
        8
  •  0
  •   idealvin    4 年前

    我们不应该使用 std::函数 在这里,创建std::函数的对象相当缓慢。

    Here 是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;
    }
    
        9
  •  -1
  •   Bert Young    10 年前

    使用方式如下:

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