65.9K
CodeProject 正在变化。 阅读更多。
Home

C++11 中委托的实现。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (20投票s)

2012年5月14日

CPOL

4分钟阅读

viewsIcon

79015

downloadIcon

788

这是“成员函数指针和最快的 C++ 委托”的替代方案。

引言

在 C++ 中,能够将一个对象与其成员函数绑定并生成一个全局函数会很有用。其他语言中存在此类功能:例如,C# 中的委托允许程序员完成此操作。在 C++ 中,存在成员函数指针,但它们不提供此功能。在本文中,我想提出一种委托的简单实现,它使用 C++ 成员函数指针和 C++11 可变参数模板。目前,此实现仅在 GNU C++ 4.7.0 中有效。在 Windows 上,您可以使用 MinGW 来运行 GNU C++。

背景

该方法是提供一个名为 create_delegate 的函数,可以按以下方式调用:

  • create_delegate(&object, &member_function)
  • create_delegate(&function)

第一种选择创建一个具有 operator() 成员的对象,该对象可以像函数一样被调用。第二种选择生成一个函数指针,这与 &function 相同。这两种值都与 function<...> 类型兼容。

一个示例程序

让我们定义一个具有多个方法的类

class A
{
 int i;
public: 
 A(int k):i(k) {}

 auto get()const ->int { return i;} 
 auto set(int v)->void { i = v;}

 auto inc(int g)->int& { i+=g; return i;}
 auto incp(int& g)->int& { g+=i; return g;}

 auto f5 (int a1, int a2, int a3, int a4, int a5)const ->int
 {
  return i+a1+a2+a3+a4+a5;
 }

 auto set_sum4(int &k, int a1, int a2, int a3, int a4)->void
 {
  i+=a1+a2+a3+a4;
  k = i;
 }

 auto f8 (int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) const ->int
 {
  return i+a1+a2+a3+a4+a5+a6+a7+a8;
 } 

 static auto sqr(double x)->double { return x*x; }

 auto g1(const int& n)->void { std::cout << "g1: " << n <<  std::endl; }
 auto g2(int&& n)->void { std::cout << "g2: " << n <<  std::endl; }
 auto g3(int& n)->int&  { n++; return n; }
 auto g4(int n)->int    { return 10*n; }
};

请注意,您不必为使用 auto 的函数使用 C++ 语法。您可以使用“传统”方式定义它们。在程序中,我们可以按如下方式创建类:

A a(11);

现在,我们可以创建委托了

auto set1 = create_delegate(&a,&A::set);
auto inc = create_delegate(&a,&A::inc);
std::function<int(int&)> incp = create_delegate(&a,&A::incp);
auto af5  = create_delegate(&a,&A::f5);
auto set_sum4= create_delegate(&a,&A::set_sum4);
auto af8  = create_delegate(&a,&A::f8);
auto sqr = create_delegate(&A::sqr); // static function, not a member
auto g1  = create_delegate(&a,&A::g1);  
auto g2  = create_delegate(&a,&A::g2);  
auto g3  = create_delegate(&a,&A::g3);  
auto g4  = create_delegate(&a,&A::g4);  

如所示,我们可以使用 autofunction<...>。现在我们可以调用委托了

set1(25);
int x = 5;
int k = inc(x);
int k2 = incp(x);
k2 *= 7;
std::cout << "a.get():" << a.get() << std::endl;
std::cout << "k: " << k << std::endl;
std::cout << "x: " << x << std::endl;
std::cout << "af5(1,2,3,4,5): " << af5(1,2,3,4,5) << std::endl;

set_sum4(x,1,2,3,20);
std::cout << "after set_sum4(x,1,2,3,20)" << std::endl;
std::cout << "a.get(): " << a.get() << std::endl;
std::cout << "x: " << x << std::endl;
std::cout << "af8(1,2,3,4,5,6,7,8): " << af8(1,2,3,4,5,6,7,8) << std::endl;
std::cout << "sqr(2.1): " << sqr(2.1) << std::endl;
g1(100);
g2(32+5);
int p = 15;
int& g = g3(p);
int s = g4(35);
g++;
std::cout << "p: " << p << std::endl;
std::cout << "s: " << s << std::endl;

程序将打印

a.get():30
k: 30
x: 35
af5(1,2,3,4,5): 45
after set_sum4(x,1,2,3,20)
a.get(): 56
x: 56
af8(1,2,3,4,5,6,7,8): 92
sqr(2.1): 4.41
g1: 100
g2: 37
p: 17
s: 350

关于实现的几点说明

对于一个简单的非 volatile 或 const 的成员函数,实现会非常简单。我们必须创建一个类来存储两个指针:一个指向对象,另一个指向其成员函数

template <class T, class R, class ... P>
struct  _mem_delegate
{
    T* m_t;
    R  (T::*m_f)(P ...);
    _mem_delegate(T* t, R  (T::*f)(P ...) ):m_t(t),m_f(f) {}
    R operator()(P ... p) 
    {
            return (m_t->*m_f)(p ...);
    }
};   

但是 return 语句需要一些修正。问题是,如果一个函数具有右值引用参数(如 auto g2(int&& n)->void),这将不起作用。函数体中的此类参数不再是右值,而是左值。这里有两种选择:

(1) 使用转发 return (m_t->*m_f)(std::forward<P>(p) ...);

(2) 仅使用 static_cast 进行转换 return (m_t->*m_f)(static_cast<P>(p) ...);

这两种选择在此处都适用。第一种强调了理念,第二种更通用。

可变参数模板允许我们定义具有灵活数量和类型的参数的 operator()create_function 的实现将简单地返回该类的对象

template <class T, class R, class ... P>
_mem_delegate<T,R,P ...> create_delegate(T* t, R (T::*f)(P ...))
{
    _mem_delegate<T,R,P ...> d(t,f);
    return d;
}

实际上,我们需要另外三个实现来覆盖 const、volatile 和 const volatile 成员函数的场景。这就是为什么传统的宏,使用 #define,很有用:它们允许我们避免重写相同的代码片段。这是完整的实现:

template <class F>
F* create_delegate(F* f)
{
    return f;
}

#define _MEM_DELEGATES(_Q,_NAME)\
template <class T, class R, class ... P>\
struct _mem_delegate ## _NAME\
{\
    T* m_t;\
    R  (T::*m_f)(P ...) _Q;\
    _mem_delegate ## _NAME(T* t, R  (T::*f)(P ...) _Q):m_t(t),m_f(f) {}\
    R operator()(P ... p) _Q\
    {\
        return (m_t->*m_f)(std::forward<P>(p) ...);\
    }\
};\
\
template <class T, class R, class ... P>\
    _mem_delegate ## _NAME<T,R,P ...> create_delegate(T* t, R (T::*f)(P ...) _Q)\
{\
    _mem_delegate ##_NAME<T,R,P ...> d(t,f);\
    return d;\
}

_MEM_DELEGATES(,Z)
_MEM_DELEGATES(const,X)
_MEM_DELEGATES(volatile,Y)
_MEM_DELEGATES(const volatile,W) 

一个更有意义的例子:计算曲线的长度

在本节中,我想探讨一个实际的示例问题,并研究各种方法,以及展示委托的一些替代方法。

曲线长度计算:全局函数

函数 CurveLength 可用于计算曲线的长度,该曲线由两个函数 fx(t)fy(t) 定义,其中 t 在 [a,b] 范围内。参数 n 是我们采取的步数,它影响精度。

auto CurveLength(auto (*fx)(double)->double, auto (*fy)(double)->double, 
   double a, double b,   int n = 20000) ->double
{    
    double s = 0.0;    
    double h = (b-a)/n;
    double t = a;
    double x0 = fx(t);
    double y0 = fy(t);        
    for (int i = 0; i < n; i++)
    {
        t += h;
        double x1 = fx(t);
        double y1 = fy(t);
        double dx = x1-x0;
        double dy = y1-y0;
        s += sqrt(dx*dx + dy*dy); 
        x0 = x1;
        y0 = y1;
    };    
    return s;
}

一种简单的 C 风格方法是定义所需的曲线作为全局函数,如果我们想参数化曲线,我们将定义全局变量。我们来看一个椭圆和一个摆线。摆线的长度可以很容易地通过解析法计算。至于椭圆,如果 a 和 b 轴相等,它将是一个圆,否则其长度无法用简单的公式表示。以下是我们对两条曲线的定义:

//Cycloid 

double cycloid_a;
auto Cycloid_fx(double t)->double
{
    return cycloid_a*(1 - cos(t));
}

auto Cycloid_fy(double t)->double
{
    return cycloid_a*(t - sin(t));
}

//Ellipse
double ellipse_a;
double ellipse_b;

auto Ellipse_fx(double t)->double
{
    return ellipse_a*cos(t);
}

auto Ellipse_fy(double t)->double
{
    return ellipse_b*sin(t);
} 

这是计算其大小的示例程序:

double PI = 4*atan(1.0);

ellipse_a = 1.0;
ellipse_b = 1.0;
    
cycloid_a = 1.0;
        
std::cout << std::setprecision(10) << std::setw(10);

std::cout <<  "ellipse1: " << 
  CurveLength(Ellipse_fx, Ellipse_fy, 0.0,2*PI) << std::endl;
std::cout <<  "cycloid1: " << 
  CurveLength(Cycloid_fx, Cycloid_fy, 0.0,2*PI) << std::endl;

ellipse_a = 3.0;
ellipse_b = 1.0;
    
cycloid_a = 5.0;
        
std::cout <<  "ellipse2: " << 
  CurveLength(Ellipse_fx, Ellipse_fy, 0.0,2*PI) << std::endl;
std::cout <<  "cycloid2: " << 
  CurveLength(Cycloid_fx, Cycloid_fy, 0.0,2*PI) << std::endl; 

程序将打印

ellipse1: 6.283185281
cycloid1: 7.999999992
ellipse2: 13.36489317
cycloid2: 39.99999996

这种方法的缺点是所有函数及其参数都是全局的。

抽象类和虚函数

一种传统的、好的 C++ 方法是使用抽象类并定义一个成员函数 CurveLength,该函数使用成员函数 fx(t)fy(t)

class Curve
{
public:
    virtual auto fx(double t)->double = 0;
    virtual auto fy(double t)->double = 0;

    auto CurveLength(double a, double b, int n = 20000)->double
    {    
        double s = 0.0;    
        double h = (b-a)/n;
        double t = a;
        double x0 = fx(t);
        double y0 = fy(t);        
        for (int i = 0; i < n; i++)
        {
            t += h;
            double x1 = fx(t);
            double y1 = fy(t);
            double dx = x1-x0;
            double dy = y1-y0;
            s += sqrt(dx*dx + dy*dy); 
            x0 = x1;
            y0 = y1;
        };    
        return s;
    }
};

class Cycloid: public Curve
{
    double m_a;
public:
    Cycloid(double a):m_a(a) {}

    virtual auto fx(double t)->double
    {
        return m_a*(1 - cos(t));
    }

    virtual auto fy(double t)->double
    {
        return m_a*(t - sin(t));
    }
};

class Ellipse: public Curve
{
    double m_a;
    double m_b;
public:
    Ellipse(double a, double b):m_a(a),m_b(b) {}

    virtual auto fx(double t)->double
    {
        return m_a*cos(t);
    }

    virtual auto fy(double t)->double
    {
        return m_b*sin(t);
    }
}; 

程序将如下所示:

double PI = 4*atan(1.0);

Ellipse ellipse1(1.0,1.0);
Cycloid cycloid1(1.0);
        
std::cout << std::setprecision(10) << std::setw(10);

std::cout <<  "ellipse1: " << 
  ellipse1.CurveLength(0.0,2*PI) << std::endl;
std::cout <<  "cycloid1: " << 
  cycloid1.CurveLength(0.0,2*PI) << std::endl;

Ellipse ellipse2(3.0,1.0);
Cycloid cycloid2(5.0);        

std::cout <<  "ellipse2: " << 
  ellipse1.CurveLength(0.0,2*PI) << std::endl;
std::cout <<  "cycloid2: " << 
  cycloid1.CurveLength(0.0,2*PI) << std::endl; 

问题在于,所有曲线都必须继承自同一个基类,如果我们想在库中使用 CurveLength,那么 Curve 类将成为其一部分。

使用委托

委托使我们能够处理不相关的类。这是使用委托的方法:

auto CurveLength(std::function<double(double)> fx, 
  std::function<double(double)> fy, double a, double b, int n = 20000)->double
{    
    double s = 0.0;    
    double h = (b-a)/n;
    double t = a;
    double x0 = fx(t);
    double y0 = fy(t);        
    for (int i = 0; i < n; i++)
    {
        t += h;
        double x1 = fx(t);
        double y1 = fy(t);
        double dx = x1-x0;
        double dy = y1-y0;
        s += sqrt(dx*dx + dy*dy); 
        x0 = x1;
        y0 = y1;
    };    
    return s;
}

double PI = 4.0*atan(1.0);

class Cycloid
{
    double m_a;
public:
    Cycloid(double a):m_a(a) {}

    auto fx(double t)->double
    {
        return m_a*(1 - cos(t));
    }

    auto fy(double t)->double
    {
        return m_a*(t - sin(t));
    }
};

class Ellipse
{
    double m_a;
    double m_b;
public:
    Ellipse(double a, double b):m_a(a),m_b(b) {}

    auto getX(double t)->double
    {
        return m_a*cos(t);
    }

    auto getY(double t)->double
    {
        return m_b*sin(t);
    }
}; 

我故意为成员函数赋予了不同的名称。这两个类是不相关的。这是一个程序:

int main()
{
 
 Ellipse ellipse1(1.0,1.0);
 Cycloid cycloid1(1.0);
        
        auto ellipse1_fx = create_delegate(&ellipse1,&Ellipse::getX);
 auto ellipse1_fy = create_delegate(&ellipse1,&Ellipse::getY);   
        auto cycloid1_fx = create_delegate(&cycloid1,&Cycloid::fx);
 auto cycloid1_fy = create_delegate(&cycloid1,&Cycloid::fy);        

        std::cout << std::setprecision(10) << std::setw(10);

 std::cout <<  "ellipse1: " << 
   CurveLength(ellipse1_fx, ellipse1_fy, 0.0,2*PI) << std::endl;
 std::cout <<  "cycloid1: " << 
   CurveLength(cycloid1_fx, cycloid1_fy, 0.0,2*PI) << std::endl;

 Ellipse ellipse2(3.0,1.0);
 Cycloid cycloid2(5.0);
        auto ellipse2_fx = create_delegate(&ellipse2,&Ellipse::getX);
 auto ellipse2_fy = create_delegate(&ellipse2,&Ellipse::getY);   
        auto cycloid2_fx = create_delegate(&cycloid2,&Cycloid::fx);
 auto cycloid2_fy = create_delegate(&cycloid2,&Cycloid::fy);        

 std::cout <<  "ellipse2: " << 
   CurveLength(ellipse2_fx, ellipse2_fy, 0.0,2*PI) << std::endl;
 std::cout <<  "cycloid2: " << 
   CurveLength(cycloid2_fx, cycloid2_fy, 0.0,2*PI) << std::endl;


 return 0;
}

委托允许我们为各种完全不相关的曲线类使用相同的函数。这个 CurveLength 可以成为库的一部分。

一种替代方法:使用 lambda 表达式

C++11 的 lambda 表达式使得可以立即定义函数,并且还可以捕获环境中的变量。CurveLength 和曲线类可以与委托示例中的完全相同地定义,但程序和函数调用会有所不同:

int main()
{
	double PI = 4.0*atan(1.0);
	Ellipse ellipse1(1.0,1.0);
	Cycloid cycloid1(1.0);
        
        auto ellipse1_fx = [&ellipse1](double t){ return ellipse1.getX(t); };
	auto ellipse1_fy = [&ellipse1](double t){ return ellipse1.getY(t); };   
        auto cycloid1_fx = [&](double t){ return cycloid1.fx(t); };
	auto cycloid1_fy = [&](double t){ return cycloid1.fy(t); };        

        std::cout << std::setprecision(10) << std::setw(10);

	std::cout <<  "ellipse1: " << 
	  CurveLength(ellipse1_fx, ellipse1_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid1: " << 
	  CurveLength(cycloid1_fx, cycloid1_fy, 0.0,2*PI) << std::endl;

	Ellipse ellipse2(3.0,1.0);
	Cycloid cycloid2(5.0);
        auto ellipse2_fx = [&ellipse2](double t){ return ellipse2.getX(t); };
	auto ellipse2_fy = [&ellipse2](double t){ return ellipse2.getY(t); };   
        auto cycloid2_fx = [&cycloid2](double t){ return cycloid2.fx(t); };
	auto cycloid2_fy = [&cycloid2](double t){ return cycloid2.fy(t); };       

	std::cout <<  "ellipse2: " << 
	  CurveLength(ellipse2_fx, ellipse2_fy, 0.0,2*PI) << std::endl;
	std::cout <<  "cycloid2: " << 
	  CurveLength(cycloid2_fx, cycloid2_fy, 0.0,2*PI) << std::endl;


	return 0;
}

我故意展示了两种捕获变量的方法:一种特定的 [&ellipse1],一种通用的 [&]。两者都可以使用。Lambda 表达式易于使用,但如果您需要处理更多参数,语法会变得更长。最终,这取决于您的选择。

致谢

我想感谢所有对本文早期版本发表评论的人。我尤其感谢 Emilio Garavaglia 对我的完美转发的宝贵意见。

参考文献

  1. https://codeproject.org.cn/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
  2. https://codeproject.org.cn/Articles/11015/The-Impossibly-Fast-C-Delegates
  3. https://codeproject.org.cn/Articles/13287/Fast-C-Delegate
© . All rights reserved.