VC中function函數解析

来源:https://www.cnblogs.com/albizzia/archive/2018/05/17/9048898.html
-Advertisement-
Play Games

C++標準庫是日常應用中非常重要的庫,我們會用到C++標準庫的很多組件,C++標準庫的作用,不單單是一種可以很方便使用的組件,也是我們學習很多實現技巧的重要寶庫。我一直對C++很多組件的實現擁有比較強的興趣。最近花了一些時間,查看了C++中function類的實現,將其中的要點,寫在這裡(這裡只介紹 ...


C++標準庫是日常應用中非常重要的庫,我們會用到C++標準庫的很多組件,C++標準庫的作用,不單單是一種可以很方便使用的組件,也是我們學習很多實現技巧的重要寶庫。我一直對C++很多組件的實現擁有比較強的興趣。最近花了一些時間,查看了C++中function類的實現,將其中的要點,寫在這裡(這裡只介紹其中的一部分):

1.首先VC實現了將<Ret(T1, T2, ...)>這種類型的類型參數,改變為<Ret, T1, T2, ...>這種類型的類型參數。使用的方法如下:

template <class _Fty>
class function : public _Get_function_impl<_Fty>::type
{
public:
    using _Mybase = _Get_function_impl<_Fty>::type;
public:
    function() noexcept
    {    // construct empty function wrapper
    }

    template <class _Fx,
         class = typename _Mybase::_Enable_if_callable_t<_Fx&, function>>
    function(_Fx _Func)
    {    // construct wrapper holding copy of _Func
    this->_Reset(std::move(_Func));
    }

private:
    // 沒有其他的數據成員
};

不過,對於_Get_function_impl<_Fty>::type的實現,應該是編譯器額外處理(我不記得C++中有類似的語法),大致處理如下(如下代碼不能編譯通過):

template <class _Ret,
    class... _Types>
struct _Get_function_impl<_Ret CALL_OPT (_Types...)>
{
    using type = _Func_class<_Ret, _Types...>;
};

參考以上代碼,也可以使用boost中的boost::typeindex,可以知道,function的實現,繼承於_Func_class,function的實現,使用了類似於Adapter的方式,具體的實現細節在_Func_class中。這點,可以參考上面的function構造函數。function中沒有直接定義operator()函數,operator()函數在_Func_class中定義,我們查看一下_Func_class函數:

using max_align_t =  double;    // most aligned type
// size in pointers of std::function and std::any (roughly 3 pointers larger than std::string when building debug
constexpr int _Small_object_num_ptrs = 6 + 16 / sizeof(void *);
constexpr size_t _Space_size = (_Small_object_num_ptrs - 1) * sizeof(void*);

template <class _Ret,
    class... _Types>
    class _Func_class
{   // implement function template
public:
    using result_type = _Ret;

    using _Ptrt = _Func_base<_Ret, _Types...>;

    _Func_class() noexcept
    {    // construct without stored object
    _Set(0);
    }

    _Ret operator()(_Types... _Args) const
    {
    if (_Empty())
    {
        _Xbad_function_call();
    }
    const auto _Impl = _Getimpl();
    return (_Impl->_Do_call(_STD forward<_Types>(Args)...));
    }

protected:
    // 用於判斷傳入的函數對象可以調用_Types...表示的參數,並且返回_Ret類型的參數,
    // 而且不為function類型(至少上面的調用時是這個意思)
    template<class _Fx,
    class _Function>
    using _Enable_if_callable_t = enable_if_t<conjunction_v<negation<is_same<decay_t<_Fx>, _Function>>,
        _Is_invocable_r<_Ret, _Fx, _Types...>>>;

    bool _Empty() const _NOEXCEPT
    {    // return true if no stored object
    return (_Getimpl() == 0);
    }

    template <class _Fx>
    void _Reset(_Fx&& _Val)
    {    // store copy of _Val
    if (!_Test_callable(_Val))
    {   // null member pointer/function pointer/std::function
        return;    // already empty
    }

    using _Impl = _Func_impl_no_alloc<decay_t<_Fx>, _Ret, _Types...>;
    _Reset_impl<_Impl>(std::forward<_Fx>(_Val), _Is_large<_Impl>());
    }

    template <class _Myimpl, class _Fx>
    void _Reset_impl(_Fx&& _Val, true_type)
    {    // store copy of _Val, large (dynamically allocated)
    _Set(_Global_new<_Myimpl>(std::foward<_Fx>(_Val)));
    }

    template <class _Myimpl, class _Fx>
    void _Reset_impl(_Fx&& _Val, false_type)
    {    // store copy of _Val, small (locally stored)
    // placement operator new,將對象創建在_Mystorage所在的地址
    _Set(::new (_Getspace()) _Myimpl(std::forward<_Fx>(_Val)));
    }

    void _Tidy() noexcept
    {    // clean up
    if (!_Empty())
    {   // destroy callable object and maybe delete it
        _Getimpl()->_Delete_this(!_Local());
        _Set(0);
    }
    }
        

private:
    union _Storage
    {    // storage for small objects (basic_string is small)
    max_align_t _Dummy1;    // for maximum alignment
    char _Dummy2[_Space_size];    // to permit aliasing
    _Ptrt *_Ptrs[_Small_object_num_ptrs];        // _Ptrs[_Small_object_num_ptrs - 1] is reserved
    };

    _Storage _Mystorage;    // 數據成員

    bool _Local() const noexcept
    {    // test for locally stored copy of object
    return (_Getimpl() == _Getspace());
    }

    _Ptrt* _Getimpl() const noexcept
    {    // get pointer to object
    return (_Mystorage._Ptrs[_Small_object_num_ptrs - 1]);
    }

    void _Set(_Ptrt* _Ptr) noexcept
    {    // store pointer to object
    _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr;
    }

    void *_Getspace() noexcept
    {    // get pointer to storage space
    return (&_Mystorage);
    }

    const void* _Getspace() const noexcept
    {
    return (&_Mystorage);
    }
};

這個類只有一個數據成員,就是_Storage _Mystorage;其中_Storage是個union,union中兩個成員用了Dummy開頭,dummy的意思,就是只是為了實現某些目的,實際中並不會應用,_Dummy1的目的是為了讓這個對象最大對齊,_Dummy2說是用於別名,我從實現來看,更像用來表示多大的函數對象可以本地存儲,_Space_size表示的就是可以將函數對象直接存儲到_Func_class中最大的值,而_Ptrs[_Small_object_num_ptrs]的大小,剛好比_Dummy2對一個指針的長度大小,因為最後一個指針需要用了存儲實際函數的起始地址,這點,可以參考_Getimpl函數和_Set函數。這樣做的目的,應該是減少new的次數,因為new的次數過多,容易導致比較嚴重的碎片化,而且new本來速度也比不了在堆棧中分配記憶體的速度,不過,另一方面,從實現來看,一個function占用的大小,遠大於一個函數指針的大小,更遠大於一個沒有數據成員的函數對象的大小。如果對於記憶體占用有很大的要求,而且需要的函數對象又特別多,例如附帶函數的事件處理隊列等,需要慎重考慮一下。

說了這麼多,那麼C++中是如何做到在函數對象比較小的情況下,將函數對象存儲到本地,而比較大的時候,將函數對象在堆上分配呢?我們需要查看一下:

_Ptrt *_Ptrs[_Small_object_num_ptrs];中的_Ptrt,也就是_Func_base:

template <class _Rx,
     class... _Types>
class _Func_base
{    // abstract base for implementation types
public:
    virtual _Func_base* _Copy(void*) const = 0;
    virtual _Func_base* _Move(void*) const = 0;
    virtual _Fx _Do_call(Types&&...) = 0;
    virtual void _Delete_this(bool) _NOEXCEPT = 0;

    _Func_base() = default;
    _Func_base(const _Func_base&) = delete;
    _Func_base& operator=(const _Func_base&) = delete;
    // destructor non-virtual due to _Delete_this()
};

template <class _Callable,
    class _Rx,
    class... _Types>
class _Func_impl_no_alloc final : public _Func_base<_Rx, _Types...>
{        // derived class for specific implementation types that don't use allocators
public:
    using _Mybase = _Func_base<_Rx, _Types...>;
    using _Nothrow_move = is_nothrow_move_constructible<_Callable>;

    template <class _Other,
    class = enable_if_t<!is_same_v<_Func_impl_no_alloc, decay_t<_Other>>>>
    explicit _Func_impl_no_alloc(_Other&& _Val)
    : _Callee(std::forward<_Other>(_Val))
    {    // construct
    }

private:
    virtual _Mybase *_Copy(void *_Where) const override
    {    // return clone of *this
    return (_Clone(_Where, _Is_large<_Func_impl_no_alloc>()));
    }

    _Mybase *_Clone(void *, true_type) const
    {    // return clone of *this, large (dynamically allocated)
    return (_Global_new<_Func_impl_no_alloc>(_Callee));
    }

    _Mybase *_Clone(void *_Where, false_type) const
    {    // return clone of *this, small (locally stored)
    return (::new (_Where) _Func_impl_no_alloc(_Callee));
    }

    virtual _Mybase *_Move(void *_Where) override
    {    // return clone of *this
    return (::new (_Where) _Func_impl_no_alloc(std::move(_Callee)));
    }

    virtual _Rx _Do_call(_Types&&... _Args) override
    {
    return (_Invoker_ret<_Rx>::_Call(_Callee, std::forward<_Types>(_Args)...));
    }

    virtual const void *_Get() const noexcept override
    {    // return address of stored object
    return (std::addressof(_Callee));
    }

    virtual void _Delete_this(bool _Dealloc) noexcept override
    {
    this->~_Func_impl_no_alloc();
    if (_Dealloc)
    {
        _Deallocate<alignof(_Func_impl_no_alloc)>(this, sizeof(_Func_impl_no_alloc));
    }
    }

_Callable _Callee;
};

我們認真查看上述代碼,不難發現,區分在_Copy函數中的_Is_large<_Func_impl_no_alloc>(),實際調用是:_Mybase *_Clone(void *, true_type),採用的是在堆中分配一段空間,而_Mybase *_Clone(void *_Where, false_type)採用的是placement operator new,將對象創建在本地。我們查看_Func_class中的Reset系列函數,也可以發現_Is_large的使用,以及堆棧分配和本地分配的區別。下麵,給出_Is_large的實現:

template <class _Impl>
struct _Is_large
    : bool_constant<_Space_size < sizeof(_Impl)
    || !_Impl::_Nothrow_move::value>
{   // determine whether _Impl must be dynamically allocated
};

其中true_type是bool_constant<true>,而false_type是bool_constant<false>,為不同的類型。最後,還要要簡單提及一下的是:_Func_class是繼承於public _Arg_types<_Types...>,而

// 這個類的目的是提供argument_type, first_argument_type, second_argument_type,
// 因為C++17中已經為deprecated,所以,沒有查看的必要
template <class... _Types>
struct _Arg_types
{   // provide argument_type, etc. (sometimes)
};

所以,我從簡單考慮,之前沒有寫出這種繼承關係,對實際理解應該沒有什麼影響。上述,就是我的簡單介紹。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 模板方法是通過繼承實現的,在父類中定義出演算法的骨架,將不同點在子類中實現。而策略模式是通過介面實現的,策略中定義了完整的演算法。它們有點像啊…… 策略模式的定義 策略模式(Strategy Pattern),定義了一系列的演算法,將每一種演算法封裝起來並可以互相替換使用,策略模式讓演算法獨立於使用它的客戶應 ...
  • 一、分散式一致性 一個事務需要跨多個分散式節點,又要保持事務的ACID特性,需要引入協調者來統一調度所有分散式節點的執行邏輯,被調度的節點稱為參與者。 協調者負責調用參與者,並決定最終是否提交事務。基於這個思想,衍生出2PC和3PC兩種協議 二、2PC協議(Two phase commit) 二階段 ...
  • Java開源生鮮電商平臺-財務系統模塊的設計與架構(源碼可下載) 前言:任何一個平臺也好,系統也好,掙錢養活團隊這個是無可厚非的,那麼對於一個生鮮B2B平臺盈利模式( 查看:http://www.cnblogs.com/jurendage/p/9016411.html)而言, 其中財務模塊無論是對於 ...
  • Ubuntu 16.04自帶python 2.7,滿足了lightgbm最低要求 1、添加必備的一些包 sudo apt-get install python-numpy sudo apt-get install python-scipy sudo apt-get install python-ma ...
  • 此篇文章深入淺出介紹了關於高速串列收發器的幾個重要概念和註意事項,為方便知識點複習總結和後續查閱特此轉載,原文標題及鏈接為:xilinx 高速收發器Serdes深入研究 - CSDN博客 https://blog.csdn.net/u010161493/article/details/7768802 ...
  • 前言 16年畢業到現在也近兩年了,最近面試了阿裡集團(菜鳥網路,螞蟻金服),網易,滴滴,點我達,最終收到點我達,網易offer,螞蟻金服二面掛掉,菜鳥網路一個月了還在流程中...最終有幸去了網易。但是要特別感謝點我達的領導及HR,真的非常非常好,很感謝他們一直的關照和指導。 面試整體事項 需要準備的 ...
  • 導讀: 1.類方法 2.靜態方法 3.類方法、實例方法、靜態方法 1. 類方法 類對象所擁有的方法。 需要用裝飾器@classmethod來標識其為類方法,對於類方法,第一個參數必須是類對象,一般以cls作為第一個參數。 類方法調用: 類名.類方法() 示例: 使用場景: 當方法中 需要使用類對象 ...
  • PHP後端之驗證碼 前言: 打算寫一些實際開發中遇到的東西。我這個人記性不好,覺得記下來,以後就算想找,也能找得到。 PHP,可能很長一段時間都不會使用了。所以還是留一些記錄。 另外還有一些伺服器架設的總結,之後整理了,會發佈出來。 一,問題: 相信大家都有以下的經歷: 1,上學的時候,猜解家裡電腦 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...