黃爸爸狗園

本園只有sanitizer,沒有狗籠

0%

A Simplified std::function Implementation

本文為C++ Weekly Ep 333的note

首先先來看code: - Compiler Explorer

這次的目標是要土炮一個精神上與std::function差不多的function class,就如同裏頭的std::function一樣,可以把任何callable都包起來

OK,讓我們一步一步來看這段code

Primary template

首先我們必須先定義Primary template,後面會對function做partial template specialization

1
2
template <typename T>
class function;
也就是說一開始的typename T,在之後會被specialization成callable obj的type(好比說int (int,int))

class function

再來是我們的function物件

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename Ret, typename ... Param>  
class function<Ret (Param...)>
{
public:
template<typename FunctionObject>
function(FunctionObject fo)
: callable(std::make_unique<callable_impl<FunctionObject>(std::move(fo))) {}
Ret operator()(Param... param) {return callable->call(param...);}

private:
/*下面再講,這裡先不看*/
std::unique_ptr<callable_interface> callable ;
};

  • 我們可以看到Ret (Param...)會被展開成int (int, int),我們寫在main裏頭的:

    1
    2
    function<int (int, int)> func{f};
    // ^here

    typename FunctionObject會被換成你傳進去的callable object的type(),然後用你給的fo去初始化那個unique_ptr(為什麼要用unique_ptr等等會講)

callable_interface & callable_impl

  • 然後整個class function最精華的地方來了,我們先定義一個pure virtual(又稱interface) callable_interface作為我們的base

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct callable_interface {
    virtual Ret call(Param...) = 0;
    virtual ~callable_interface() = default;
    };

    template <typename Callable>
    struct callable_impl : callable_interface {
    callable_impl(Callable callable_) : callable(std::move(callable_)){}

    Ret call(Param... param) { return std::invoke(callable, param...);}
    Callable callable;
    };

  • 接下來讓callable_impl去繼承callable_interface,使其變成callable_interface的derived class,現在讓我們注意callable_impl的call(),讓我們來看一下cpp_insight的輸出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    template<typename Callable>
    struct callable_impl;
    /* First instantiated from: type_traits:1392 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    struct callable_impl<int (*)(int, int)> : public callable_interface
    {
    inline callable_impl(int (*callable_)(int, int))
    : callable_interface()
    , callable{std::move(callable_)}
    {
    }

    inline virtual int call(int __param0, int __param1)
    {
    return std::invoke(this->callable, __param0, __param1);
    }

    int (*callable)(int, int);
    // inline virtual constexpr ~callable_impl() noexcept = default;
    };

    #endif
    /* First instantiated from: type_traits:1392 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    struct callable_impl<__lambda_39_36> : public callable_interface
    {
    inline callable_impl(__lambda_39_36 callable_)
    : callable_interface()
    , callable{__lambda_39_36(std::move(callable_))}
    {
    }

    inline virtual int call(int __param0, int __param1)
    {
    return std::invoke(this->callable, __param0, __param1);
    }

    __lambda_39_36 callable;
    // inline virtual constexpr ~callable_impl() noexcept = default;
    };

我們可以發現:

  1. struct callable_impl<int (*)(int, int)>
  2. struct callable_impl<__lambda_39_36>

編譯器幫我們生出了一個raw function pointer,一個lambda的callable_impl,又因為call()是一個virtual,然後我們還使用了unique_ptr來記錄實際callable_impl的 ptr(所以這些物件都在heap上,吃的到virtual table)

這就代表無論我的FunctionObject最後是什麼,只要他能夠被call,callable_impl裏頭的call()一定就能夠透過std::invoke()產生出正確的呼叫程式碼

至此整個function object的原理就說明完了

結語

  • 你各位要hold住阿