2015-09-07 55 views
4

Golang式defer的總體構思解釋爲herehere什麼是C++中的標準延遲/終結器實現?

我想知道STL(C++ 11,C++ 14,...)或者Boost或者其他一些庫是否包含這樣的類的實現?所以我可以使用它,而不必在每個新項目中都重新實現它。

+1

的STL沒有。但是這兩個鏈接都提供了很小的C++ 11代碼樣本(幾行代碼)來實現您尋找的行爲,因此您可能會花更多時間試圖找到提供該功能的庫,而不是自己推出。 – Peter

回答

7

有一個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.

+0

當然有'unique_ptr'已經做了90%的相同的事情;此外還有Boost [ScopeExit](http://www.boost.org/doc/libs/1_59_0/libs/scope_exit/doc/html/index.html)。你能告訴我懶得真的寫出答案:) – sehe

+0

我也試圖使用unique_ptr,但它會有一個虛擬指針,我想用NULL填寫。但是如果我這樣做了,它會叫刪除者嗎? –

+0

@Jens:壞消息; [它看起來像scope_exit et。 al不會使C++ 17](http://stackoverflow.com/a/38285263/734069)。 –

0

在新標準來臨之前,我使用簡單的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以及類似的實現。

+1

不好意思,但是ScopeGuard不應該被綁定到這個例子中的任何變量嗎? –

+0

@afiskon謝謝,它應該=)編輯帖子。 – PSIAlt

+0

不幸的是,在這裏使用'std :: function'會殺死你的代碼。你真的不想這樣做。請參閱[本演示文稿](https://www.youtube.com/watch?v=lKG1m2NkANM)從10m58s開始。 – Quuxplusone

1

這裏是我的解決方案,它類似於你在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(); 
     } 
    } 
}; 

它可能看起來笨重,因爲它的使用載體,但它保留在函數被調用以相反的順序迅速的延遲的行爲:

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; 

但它是一個有點比延遲更強大因爲您可以在任何低於您自己的範圍內添加延遲呼叫。你只需要小心,當你使用這個時,捕獲的lambda不會超出範圍(有點缺陷,但如果你小心的話,不是一個嚴重的缺陷)。

我通常不會使用它,除非它是一次性聲明。理想情況下,你可以將任何資源包裝在一個新的類中,當它超出範圍時將其釋放,但如果它的頂級代碼具有大量資源和錯誤處理,則延遲從代碼可讀性的角度來看更有意義。你不必記住一大堆真正做同樣事情的新類。

-1

像這樣使用:

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

不幸的是,這裏使用'std :: function'會殺死你的codegen;你真的不想這樣做。請參閱[本演示文稿](https://www.youtube.com/watch?v=lKG1m2NkANM)從10m58s開始。 – Quuxplusone

4

我在CppCon 2014提出了一個頭,只有實現圍棋風格deferYouTube 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__作爲窮人的__COUNTER__)。至於效率方面,我從來沒有見過GCC或Clang生成任何非或-O2或更高版本的完美代碼以外的東西 - 這是那些傳說中的「零成本抽象」之一。

The original source也有一個C89版本,通過利用一些非常 GCC特定的屬性在GCC上工作。

5

與其他答案不同,此實現爲零開銷,並且在語法上更好且更易於使用。它也具有零依賴性,減少了編譯時間。

您可以將此代碼段粘貼到您的代碼庫中的任何位置,它就會正常工作。

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

PS:本地deferrer對象與zz_開始,而不是_,因此不會弄亂在調試器局部變量窗口,也因爲用戶標識符在技術上不應該以下劃線開頭。

+0

模板化(F級)有什麼好處?它總是'功能'。 –

+1

推遲塊**不需要**參數,但**通過引用捕獲整個範圍**。帶捕獲的Lambdas不能隱式轉換爲void(*)()',因爲捕獲需要存儲。使用'std :: function '可以工作,但是如果給出大的捕獲列表,堆分配就有風險。通過將捕獲保留在堆棧上,此答案可讓編譯器內聯整個塊,產生零開銷延期。 – pmttavara

+0

在godbolt上測試過,你說得對。我認爲沒有什麼會去堆,但否則使用std :: function會留下一大堆樣板,而使用模板會導致零開銷。我仍然有點驚訝,C++有兩個不同的lambda結構,std :: function和一個非特定類型的本地魔術結構。如果他們是相同的會很好。 –

0

這裏是我的延期實施,但沒有保證,我仍然認爲它不是很好的實施。

像這樣來使用:

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