2012-11-08 56 views
6

Vì vậy, tôi đang cố gắng viết một hàm Tích hợp được sử dụng với C++ 11 lambdas. Mã này trông giống như sau:C con trỏ hàm với C++ 11 lambdas

double Integrate(std::function<double(double,void*)> func, double a,double b,std::vector<double> & params) 
{ 
    gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000); 
    gsl_function F; 
    F.function =func; 
    F.params = (void*)&params; 
    double error,result; 
    gsl_integration_qag (&F, a, b, 0, 1e-7, 1000,GSL_INTEG_GAUSS61,w, &result, &error); 
    gsl_integration_workspace_free (w); 
    return result; 
} 

void Another_function() 
{ 
//... 
Integrate([](double a,void* param) 
    { 
    return ((vector<double> *)params)->at(0)*a+((vector<double> *)params)->at(1); 
    } 
    ,0,3,{2,3}); 
} 

Đang cố gắng để biên dịch này, trình biên dịch nói:

error: cannot convert ‘std::function<double(double, void*)>’ to ‘double (*)(double, void*)’ in assignment 

về dòng

F.function =func; 

Nhưng nếu tôi viết:

F.function =[](double a,void* param) 
    { 
    return ((std::vector<double> *)param)->at(0)*a+((std::vector<double> *)param)->at(1); 
    }; 

Nó biên dịch và hoạt động tốt. Làm thế nào tôi nên giải quyết điều này?

+6

Bạn có cần tính linh hoạt của 'std :: function' không? Bạn có thể thay đổi tham số đầu tiên của 'Integerate' thành một con trỏ hàm? Bởi vì không có cách nào bạn sẽ có thể sử dụng một 'std :: function' như là một con trỏ hàm, trừ khi bạn nhận được vào một số kinh doanh biến toàn cầu rất xấu xí. Lưu ý rằng bạn có thể lưu trữ một lambda trong một con trỏ hàm, miễn là nó không nắm bắt, trong đó, trong ví dụ bạn đã cho thấy, nó không có. –

+0

Có câu trả lời của bạn, trong câu cuối cùng của bình luận đầu tiên. –

+0

Trên thực tế không cần cho globals, kể từ khi giao diện C bao gồm một tham số 'void *' được thông qua. – aschepler

Trả lời

4

Sử dụng một void * là điển hình của giao diện callback C để vượt qua một số "nhà nước" để hàm. Tuy nhiên, std :: function không cần điều này vì std :: function hỗ trợ "stateful functions". Vì vậy, bạn có thể làm điều gì đó như thế này:

double Integrate(
      std::function<double(double)> func, 
      double a, double b) 
{ 
    typedef std::function<double(double)> fun_type; 
    ::: 
    F.function = [](double x, void* p){ 
     return (*static_cast<fun_type*>(p))(x); 
    }; 
    F.params = &func; 
    ::: 
} 

và lưu trữ một tham chiếu đến các vector tham số như một phần của functor sẽ được đóng gói trong các đối tượng std :: chức năng hoặc làm điều gì đó như thế này:

void Another_function() 
{ 
    double m = 2; 
    double b = 3; 
    auto func = [&](double x){return m*x+b}; 
    auto r1 = Integrate(func,0,3); 
    ::: 
} 

Tuy nhiên, giải pháp này sẽ sử dụng nhiều sự không đồng ý. GSL sẽ gọi lambda của bạn. Lambda của bạn sẽ gọi std :: function <> :: operator() mà lần lượt sẽ inwoke một số loại chức năng ảo được sử dụng cho loại tẩy xoá mà lần lượt sẽ gọi tính toán thực tế.

Vì vậy, nếu bạn quan tâm đến hiệu suất, bạn có thể loại bỏ một vài lớp ở đó, cụ thể là std :: function. Dưới đây là một cách tiếp cận khác với mẫu chức năng:

template<class Func> 
double Integrate(
      Func func, 
      double a, double b) 
{ 
    ::: 
    F.function = [](double x, void* p)->double{ 
     return (*static_cast<Func*>(p))(x); 
    }; 
    F.params = &func; 
    ::: 
} 

Tôi đoán tôi thích điều này hơn giải pháp hàm std ::.

+0

Tôi thực sự thích giải pháp cuối cùng, rất tốt đẹp! –

+0

của ':::' là gì? người giữ chỗ? – pyCthon

+1

@pyCthon: Có, nó được cho là một nơi "nhiều thứ ở đây" nơi giữ. C++ đã quá tải "..." với các ý nghĩa khác. Đó là một dấu chấm lửng và một toán tử mở rộng gói. ;) – sellibitze

3

Dường như thư viện gsl yêu cầu con trỏ hàm. Một lambda mà không nắm bắt có thể được chuyển đổi thành một con trỏ hàm. Bất kỳ lambda có thể được chuyển đổi thành std::function. Nhưng không thể chuyển đổi số std::function thành con trỏ hàm.

Bạn có thể thử:

struct functor_and_params { 
    std::function<double(double, void*)> f; 
    void* params; 
    static double invoke(double x, void* ptr) { 
     functor_and_params& f_and_p = *reinterpret_cast<functor_and_params*>(ptr); 
     return f_and_p.f(x, f_and_p.params); 
    } 
}; 

double Integrate(std::function<double(double,void*)> func, 
       double a,double b,std::vector<double> & params) { 
    functor_and_params f_and_p{ func, &params }; 
    gsl_function F; 
    F.function = &functor_and_params::invoke; 
    F.params = &f_and_p; 
    //... 
} 
3

Không thể chuyển đổi std::function<> thành con trỏ hàm. std::function<> là các đối tượng chức năng có khả năng giữ trạng thái trong khi các hàm thông thường là không trạng thái (loại, bạn có khả năng có các biến số static, nhưng đó là một điều khác).

Mặt khác, stateless lambdas thể được chuyển đổi sang một con trỏ hàm, vì vậy bạn có khả năng có thể thay đổi chữ ký của chức năng của bạn để có một con trỏ hàm trực tiếp và lambda sẽ được chuyển đổi:

double Integrate(double(*func)(double,void*), double a, double b, 
       std::vector<double> & params) // !!! 

std::vector<double> p{2,3}; 
Integrate([](double a,void* param) 
    { 
     std::vector<double> *p = static_cast<std::vector<double>*>param; 
     return p->at(0)*a+p->at(1); 
    } 
    ,0,3,p); 

Lưu ý rằng việc liên kết rvalue với tham chiếu không phải là const, vì vậy bạn không thể chuyển một cách hợp pháp {2,3} làm đối số cuối cùng cho Integrate (ngay cả khi Visual Studio cho phép bạn), bạn sẽ cần tạo tên biến.

3

Tốt nhất là để đóng gói chuyển đổi void * bên trong hàm của bạn wrapper:

double Integrate(std::function<double(double)> func, double a, double b) 
{ 
    gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000); 
    gsl_function F; 
    F.function = [](double a, void *param) { 
    return (*static_cast<std::function<double(double)> *>(param))(a); }; 
    F.params = (void*)&func; 
    double error,result; 
    gsl_integration_qag (&F, a, b, 0, 1e-7, 1000,GSL_INTEG_GAUSS61,w, &result, &error); 
    gsl_integration_workspace_free (w); 
    return result; 
} 

void Another_function() 
{ 
    //... 
    std::vector<double> params = {2, 3}; 
    Integrate([params](double a) { return (params[0]*a+params[1]; }, 0, 3); 
} 

Có một số tiền nhất định gián tiếp thừa ở đây (thông qua std::function) nhưng chi nhánh dự đoán của CPU sẽ có thể thực hiện cũng như gián tiếp sẽ luôn luôn là cùng một lambda.

3

Nếu bạn cần tích hợp hàm lambda với chụp (trong trường hợp này không có chuyển đổi sang con trỏ thô) và nếu bạn không muốn có các hình phạt hiệu suất liên kết với hàm std :: (như được chỉ ra bởi sellibitze - xem std::function vs template), bạn có thể sử dụng các wrapper sau

template< typename F > class gsl_function_pp : public gsl_function { 
public: 
gsl_function_pp(const F& func) : _func(func) { 
    function = &gsl_function_pp::invoke; 
    params=this; 
} 
private: 
const F& _func; 
static double invoke(double x, void *params) { 
return static_cast<gsl_function_pp*>(params)->_func(x); 
} 
}; 

đây là một mã kiểm tra cho thấy làm thế nào để sử dụng nó

double a = 1; 
auto ptr = [=](double x)->double{return a*x;}; 
gsl_function_pp<decltype(ptr)> Fp(ptr); 
gsl_function *F = static_cast<gsl_function*>(&Fp); 

Nếu bạn thực sự muốn sử dụng std :: chức năng, sau đó bạn có thể sử dụng phiên bản này của trình bao bọc

class gsl_function_pp : public gsl_function 
{ 
    public: 
    gsl_function_pp(std::function<double(double)> const& func) : _func(func){ 
    function=&gsl_function_pp::invoke; 
    params=this; 
    }  
    private: 
    std::function<double(double)> _func; 
    static double invoke(double x, void *params) { 
    return static_cast<gsl_function_pp*>(params)->_func(x); 
    } 
}; 

Các mã kiểm tra trong trường hợp này thậm chí còn đơn giản hơn

double a = 1; 
gsl_function_pp Fp([=](double x)->double{return a*x;}); 
gsl_function *F = static_cast<gsl_function*>(&Fp); 

Những điều tốt đẹp về những wrapper là họ cũng có thể được dùng để tích hợp chức năng thành viên lớp.