Contents

简易function

本文主要讲一下怎么实现一个简易版本的function<>模板,从c++ templates第二版摘出。

出发点

一个简单的例子,下面这个模板上述能够接受任意可调用的对象,lambda表达式、函数指针、仿函数。

1
2
3
4
5
6
7
template<typename F>
void for_int_up(int n, F f)
{
    for (int i = 0; i <= n; ++i) {
        f(i);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void print_int(int i)
{
    std::count << i << ' ';
}

int main()
{
    int sum = 0;
    for_int_up(5, [&sum](int i) {
        sum += i;
    });
    
    for_int_up(5, print_int);
    
    return 0;
}

像for_int_up这样的函数其实有两个问题:

  • 使用了模板将函数内部实现暴露。
  • 造成代码膨胀,for_int_up函数还比较小,如果是一个很大的函数,代码膨胀会厉害的多。

于是为了解决上面两个问题,可能尝试用下面这个方案,这也是我们代码中常见的解决方案。

1
2
3
4
5
6
void for_int_up(int n, void (*f)(int))
{
    for (int i = 0; i <= n; ++i) {
        f(i);
    }
}

考虑到有可能有需要带上参数的需求,我们可能会写成这样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void for_int_up(int n, void (*f)(int, void *data), void *data)
{
    for (int i = 0; i <= n; ++i) {
        f(i, data);
    }
}

void sum(int i, void *data)
{
    int *sum = (int*)data;
    *sum += i;
}

int main()
{
    int j = 0;
    for_int_up(5, sum, &j);
    return 0;
}

使用函数指针替代模板,for_int_up只接受函数指针,无法接收lambda表达式、仿函数。

带参数的方案基本上能够满足需求,只是需要将函数与数据分离,代码相对难写,强转可能存在错误。

本质上带参数void*的方案是在抹除类型信息。

基于以上的需求标准库里的std::function<>就应运而生了。

1
2
3
4
5
6
void for_int_up(int n, std::function<void(int)> f)
{
    for (int i = 0; i <= n; ++i) {
        f(i);
    }
}

std::function<void(int)>能够接收任意返回值是void,参数为int的函数指针、lambda表达式、仿函数。

上面两个问题一下就得到了解决,内部实现可以隐藏起来,同时模板范围缩小到std::function,即使for_int_up再大也不会出现代码膨胀很厉害的情况。

广义的函数指针

std::function实际上是一个广义上的C++函数指针,需要支持一下操作:

  • 在调用者只知道入参与返回值的情况,可以调用执行,不需要理解内部具体实现。
  • 支持复制、移动。
  • 可以被入参与返回值相同的函数指针、lambda表达式、仿函数、std::function初始化
  • 支持null状态

实现

下面我们会实现一个简易版本的std::function,FUNCTION_PTR。FUNCTION_PTR会支持上面提到的所有特性。

functionptr.hpp

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 原始模板,因为实际上FUNCTION_PTR模板参数只有一个,所以需要这个原始模板:
// 模板参数是一个函数类型
template < typename T >
class FUNCTION_PTR;

// 偏特化,提取出变参Args为所有参数,R为返回值
template < typename R, typename... Args >
class FUNCTION_PTR< R( Args... ) > {
   private:
    FUNCTOR_BRIDGE< R, Args... > *bridge; // 核心类,参数类型信息,提供抽象的invoke。

   public:
    // 构造:
    FUNCTION_PTR() : bridge( nullptr ) {} // 默认为空
    FUNCTION_PTR(FUNCTION_PTR const &other );  // see functionptr-cpinv.hpp
    FUNCTION_PTR(FUNCTION_PTR &other )
        : FUNCTION_PTR( static_cast< FUNCTION_PTR const & >( other ) ) {}
    FUNCTION_PTR( FUNCTION_PTR &&other ) : bridge( other.bridge )
    {
         // 移动构造,所有权转移
        other.bridge = nullptr;
    }
    // 从任意函数对象构造:
    template < typename F >
    FUNCTION_PTR( F &&f );  // see functionptr-init.hpp
    
    // 赋值操作符:
    FUNCTION_PTR &operator=( FUNCTION_PTR const &other )
    {
        FUNCTION_PTR tmp( other );
        swap( *this, tmp );
        return *this;
    }
    FUNCTION_PTR &operator=( FUNCTION_PTR &&other )
    {
        delete bridge;
        bridge = other.bridge;
        other.bridge = nullptr;
        return *this;
    }
    // 从任意函数对象复制:
    template < typename F >
    FUNCTION_PTR &operator=( F &&f )
    {
        FUNCTION_PTR tmp( std::forward< F >( f ) ); // 先构造一个临时对象
        swap( *this, tmp ); // 调用swap
        return *this;
    }
    // 析构:
    ~FUNCTION_PTR( ) { delete bridge; }
    friend void swap( FUNCTION_PTR &fp1, FUNCTION_PTR &fp2 )
    {
        std::swap( fp1.bridge, fp2.bridge );
    }
    // bool隐式转换,便于if判断
    explicit operator bool( ) const { return bridge == nullptr; }
    // ()操作符重载:
    R operator( )( Args... args ) const;  // see functionptr-cpinv.hpp
};

可以看到FUNCTION_PTR包含一个FUNCTOR_BRIDGE< R, Args… >成员,它负责存储具体的功能对象,并将具体类型擦除了。

functorbridge.hpp

1
2
3
4
5
6
7
8
// FUNCTOR_BRIDGE只是一个抽象类,定义了几个基本接口
template < typename R, typename... Args >
class FUNCTOR_BRIDGE {
   public:
    virtual ~FUNCTOR_BRIDGE( ) {}
    virtual FUNCTOR_BRIDGE *clone( ) const = 0; // 复制接口
    virtual R invoke( Args... args ) const = 0; // 调用接口
};

bridge/functionptr-cpinv.hpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 基于FUNCTOR_BRIDGE的抽象接口,我们能够实现FUNCTION_PTR复制与调用
template < typename R, typename... Args >
FUNCTION_PTR< R( Args... ) >::FUNCTION_PTR( FUNCTION_PTR const &other )
    : bridge( nullptr )
{
    if ( other.bridge ) {
        bridge = other.bridge->clone( ); // 复制
    }
}
template < typename R, typename... Args >
R FUNCTION_PTR< R( Args... ) >::operator( )( Args... args ) const
{
    return bridge->invoke( std::forward< Args >( args )... ); // 调用,完美转发
}

bridge/specificfunctorbridge.hpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 真正存储实际的可调用对象,继承自FUNCTOR_BRIDGE,利用多态实现类型的擦除
template < typename FUNCTOR, typename R, typename... Args >
class SPECIFIC_FUNCTOR_BRIDGE : public FUNCTOR_BRIDGE< R, Args... > {
    FUNCTOR functor;

   public:
    // 构造
    // 这里用了一个单独的FUNCTOR_FWD而不是FUNCTOR,因为有可能FUNCTOR_FWD类型与FUNCTOR
    // 不一样,存在隐式转换
    template < typename FUNCTOR_FWD >
    SPECIFIC_FUNCTOR_BRIDGE( FUNCTOR_FWD &&functor )
        : functor( std::forward< FUNCTOR_FWD >( functor ) ) 
    {
    }
    virtual SPECIFIC_FUNCTOR_BRIDGE *clone( ) const override
    {
        return new SPECIFIC_FUNCTOR_BRIDGE( functor );
    }
    virtual R invoke( Args... args ) const override
    {
        return functor( std::forward< Args >( args )... );
    }
};

functionptr-init.hpp

1
2
3
4
5
6
7
8
9
// 任意类型转换为FUNCTION_PTR
template < typename R, typename... Args >
template < typename F >
FUNCTION_PTR< R( Args... ) >::FUNCTION_PTR( F &&f ) : bridge( nullptr )
{
    using FUNCTOR = std::decay_t< F >; // 类型退化
    using BRIDGE = SPECIFIC_FUNCTOR_BRIDGE< FUNCTOR, R, Args... >;
    bridge = new BRIDGE( std::forward< F >( f ) ); // 生成brige
}

到此基本上FUNCTION_PTR已经实现完成了。

其他

  • 最终版本欠缺一个小功能判断FUNCTION_PTR内部存储的可调用对象是否相等。
  • FUNCTION_PTR将所有可调用对象都转换为了一次虚函数的调用,降低了性能。
  • 每创建一个FUNCTION_PTR都需要一次堆内存分配。
  • gcc4.8中的std::function对于小对象不会进行堆内存的分配。