当前位置: 动力学知识库 > 问答 > 编程问答 >

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

问题描述:

General idea of Golang-style defer is explained here and here.

I wonder, does STL (of C++11, C++14, ...) or maybe Boost or maybe some other library contain implementation of such a class? So I could just use it without reimplementing it in every new project.

网友答案:

There is a proposal for std::unique_resource_t which will enable code like

auto file=make_unique_resource(::fopen(filename.c_str(),"w"),&::fclose);

for resources, and it defines a general scope_exit, which should be the same as defer:

// Always say goodbye before returning,
auto goodbye = make_scope_exit([&out]() ->void
{
out << "Goodbye world..." << std::endl;
});

It looks like it will be part of the next standard.

网友答案:

I presented a header-only implementation of Go-style defer at CppCon 2014 (YouTube link); I called it Auto. IMHO this is still far and away the best alternative out there in terms of teachability, efficiency, and absolute fool-proofness. In use, it looks like this:

#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 looks like this:

#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__)

Yes, that's the entire file: just 15 lines of code! It requires C++11 or newer, and requires your compiler to support __COUNTER__ (although you can use __LINE__ as a poor man's __COUNTER__ if you need portability to some compiler that doesn't support it). As for efficiency, I've never seen GCC or Clang generate anything other than perfect code for any use of Auto at -O2 or higher — it's one of those fabled "zero-cost abstractions."

The original source also has a C89 version that works on GCC by exploiting some very GCC-specific attributes.

网友答案:

Here's my solution, which is similar to the type you'd encounter in swift, but I don't handle any exceptions (easy enough to add if required, just use a try/catch block like in PSIAlt's solution):

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();
        }
    }
};

It may seem clunky due to its use of vector, but it retains the behavior of swift's defer where the functions are called in reverse order:

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;

But it's a bit more powerful than defer in that you can add deferred calls at any scope lower than your own. You just have to be careful that the lambda captured doesn't go out of scope when you use this (a bit of a flaw but not a serious one if you're careful).

I normally wouldn't use this, unless it's going to be a one-off statement. Ideally you'd wrap any resource around a new class that deallocates it when it goes out of scope, but if its top-level code with lots of resources and error handling, defer does make more sense from a code readability standpoint. You don't have to remember a bunch of new classes that really all do the same thing.

网友答案:

Before new standard comes, I use simple RAII class for this:

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

Later it can be used for any things, for example:

FILE *fp = fopen(filename.c_str(),"w");
if(fp==NULL) throw invalid_argument();
ScopeGuard _fp_guard([&fp]() {
    fclose(fp);
});

Also, you can use Boost.ScopeExit and similar implementations.

网友答案:

This implementation is zero-overhead unlike some other answers, and syntactically nicer and easier to use than other answers. It has zero dependencies as well, reducing compile times.

You can copy-paste this snippet anywhere in your codebase and it will just work.

#ifndef defer
template <class F> struct deferrer {
    F f;
    ~deferrer() { f(); }
};
struct defer_dummy {};
template <class F> deferrer<F> operator+(defer_dummy, F f) { return {f}; }
#define DEFER_CAT_(a, b) a##b
#define DEFER_CAT(a, b) DEFER_CAT_(a, b)
#define defer auto DEFER_CAT(_defer_, __LINE__) = defer_dummy{} + [&]() -> void
#endif // defer

Usage: defer { statements; };

Example:

#include <cstdint>
#include <cstdio>
#include <cstdlib>

#ifndef defer
template <class F> struct deferrer {
    F f;
    ~deferrer() { f(); }
};
struct defer_dummy {};
template <class F> deferrer<F> operator+(defer_dummy, F f) { return {f}; }
#define DEFER_CAT_(a, b) a##b
#define DEFER_CAT(a, b) DEFER_CAT_(a, b)
#define defer auto DEFER_CAT(_defer_, __LINE__) = defer_dummy{} + [&]() -> void
#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;
}
网友答案:

used like this:

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

My implementation( please add header files yourself):

 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) = [&] () 
分享给朋友:
您可能感兴趣的文章:
随机阅读: