2013-04-11 41 views
8

trình duyệt thời gian dài, người hỏi lần đầu tại đây. Tôi đã viết một số script để thực hiện các phương thức tích hợp số 1D khác nhau và biên dịch chúng thành một thư viện. Tôi muốn thư viện đó linh hoạt nhất có thể về những gì nó có khả năng tích hợp.C++: chức năng chuyển với số tham số tùy ý dưới dạng tham số

Ở đây tôi bao gồm một ví dụ: một ví dụ quy tắc hình thang rất đơn giản, nơi tôi chuyển con trỏ đến hàm được tích hợp.

// Numerically integrate (*f) from a to b 
// using the trapezoidal rule. 
double trap(double (*f)(double), double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += (*f)(xi); } 
    else { s += 2*(*f)(xi); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Điều này rất hữu ích cho các chức năng đơn giản chỉ thực hiện một đối số. Ví dụ:

double a = trap(sin,0,1); 

Tuy nhiên, đôi khi tôi có thể muốn tích hợp thứ gì đó có nhiều tham số hơn, như đa thức bậc hai. Trong ví dụ này, các hệ số sẽ được xác định bởi người dùng trước khi tích hợp. mã ví dụ:

// arbitrary quadratic polynomial 
double quad(double A, double B, double C, double x) { 
    return (A*pow(x,2) + B*x + C); 
} 

Lý tưởng nhất, tôi sẽ có thể làm điều gì đó như thế này để tích hợp nó:

double b = trap(quad(1,2,3),0,1); 

Nhưng rõ ràng đó không làm việc. Tôi đã nhận được xung quanh vấn đề này bằng cách định nghĩa một lớp học có các hệ số như các thành viên và các chức năng hấp dẫn như một hàm thành viên:

class Model { 
    double A,B,C; 
public: 
    Model() { A = 0; B = 0; C = 0; } 
    Model(double x, double y, double z) { A = x; B = y; C = z; } 
    double func(double x) { return (A*pow(x,2)+B*x+C); } 
}; 

Tuy nhiên, sau đó chức năng hội nhập của tôi cần phải thay đổi để có một đối tượng như là đầu vào thay vì con trỏ hàm:

// Numerically integrate model.func from a to b 
// using the trapezoidal rule. 
double trap(Model poly, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += poly.func(xi); } 
    else { s += 2*poly.func(xi); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Điều này hoạt động tốt, nhưng thư viện kết quả không được xác định vì cần mô hình lớp ở đâu đó. Ngoài ra, lý tưởng là Mô hình sẽ có thể thay đổi từ người dùng sang người dùng vì vậy tôi sẽ không muốn sửa nó trong một tệp tiêu đề. Tôi đã cố gắng sử dụng các mẫu hàm và functors để làm việc này nhưng nó không phải là rất độc lập kể từ lần nữa, mẫu nên được định nghĩa trong một tệp tiêu đề (trừ khi bạn muốn khởi tạo một cách rõ ràng, mà tôi không làm). Vì vậy, để tổng hợp: có cách nào tôi có thể nhận các chức năng tích hợp để chấp nhận các hàm 1D tùy ý với số tham số đầu vào trong khi vẫn còn độc lập đủ để chúng có thể được biên dịch thành một thư viện độc lập không? Không. Cảm ơn trước cho các đề xuất.

+0

['std :: bind'] (http://en.cppreference.com/w/cpp/utility/functional/bind) –

Trả lời

8

Điều bạn cần là các mẫu và std::bind() (hoặc đối tác boost::bind() nếu bạn không đủ khả năng C++ 11). Ví dụ, đây là những gì chức năng trap() của bạn sẽ trở thành:

template<typename F> 
double trap(F&& f, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += f(xi); } 
//        ^
    else { s += 2* f(xi); } 
//    ^
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Thông báo, rằng chúng ta đang khái quát hóa từ con trỏ chức năng và cho phép bất kỳ loại đối tượng callable (bao gồm một lambda C++ 11, ví dụ) để được truyền vào. Do đó, cú pháp để gọi hàm do người dùng cung cấp không phải là *f(param) (chỉ hoạt động với các con trỏ hàm), nhưng chỉ f(param).

Liên quan đến tính linh hoạt, chúng ta hãy xem xét hai chức năng mã hóa cứng (và giả vờ họ có ý nghĩa):

double foo(double x) 
{ 
    return x * 2; 
} 

double bar(double x, double y, double z, double t) 
{ 
    return x + y * (z - t); 
} 

Bạn bây giờ có thể cung cấp cả chức năng đầu tiên trực tiếp ở đầu vào để trap(), hoặc kết quả của ràng buộc cuối cùng ba đối số của hàm thứ hai đối với một số giá trị cụ thể (bạn tự do lựa chọn mà lập luận để ràng buộc):

#include <functional> 

int main() 
{ 
    trap(foo, 0, 42); 
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42); 
} 

Tất nhiên, bạn có thể nhận được linh hoạt hơn với lambdas:

#include <functional> 
#include <iostream> 

int main() 
{ 
    trap(foo, 0, 42); 
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42); 

    int x = 1729; // Or the result of some computation... 
    int y = 42; // Or some particular state information... 
    trap([&] (double d) -> double 
    { 
     x += 42 * d; // Or some meaningful computation... 
     y = 1; // Or some meaningful operation... 
     return x; 
    }, 0, 42); 

    std::cout << y; // Prints 1 
} 

Và bạn cũng có thể vượt qua functors stateful của riêng bạn tp trap(), hoặc một số đối tượng callable bọc trong một đối tượng std::function (hoặc boost::function nếu bạn không thể đủ khả năng C++ 11). Sự lựa chọn là khá rộng.

Đây là số live example.

+0

Cảm ơn! Tôi thích ví dụ mẫu vì nó rất tao nhã, nhưng tôi không chắc nó sẽ làm việc cho tôi - sự hiểu biết của tôi là một hàm mẫu phải được định nghĩa và khai báo ở cùng một chỗ, trong khi tôi muốn định nghĩa 'trap () 'trong một thư viện riêng và sau đó khai báo nó để sử dụng trong các dự án khác. Tôi đã đề nghị của bạn bằng cách sử dụng 'std :: function' kết hợp với' std :: bind' và redefined 'trap()' như 'double trap (std :: function f, double a, double b) '; phần khó chịu duy nhất là tôi phải xác định lại các hàm đơn giản như sin như 'std :: function'. – t354

+2

@ t354: Có, các mẫu bị vấn đề phân tách: định nghĩa phải nằm trong tệp tiêu đề. Ngoài ra, bạn * có thể * đặt các định nghĩa trong một tệp '.cpp' và cung cấp cái gọi là * instantiations * của mẫu của bạn cho tất cả các đối số có thể được sử dụng trong ứng dụng của bạn, nhưng nếu thư viện của bạn là thư viện thì không thể dự đoán mẫu của bạn sẽ được khởi tạo như thế nào. Đặt 'std :: function' trong chữ ký là OK: lưu ý rằng có một chi phí thời gian chạy, vì vậy bạn sẽ phải đo lường xem nó có phải là chi phí liên quan cho bạn hay không. –

+1

@ t354: Tôi không hiểu phần cuối cùng mặc dù: bạn có ý gì bằng cách "xác định lại các hàm đơn giản như' sin' "? Nếu bạn có nghĩa là: 'std :: function = sin; bẫy (sin, 0, 42); ', hơn là không cần thiết. Bạn có thể gọi trực tiếp 'trap (sin, 0, 42);', không khai báo đối tượng 'std :: function'. –

3

Những gì bạn đang cố gắng làm là làm cho điều này có thể

trap(quad, 1, 2, 3, 0, 1); 

Với C++ 11 chúng tôi có mẫu bí danh và mẫu variadic

template< typename... Ts > 
using custom_function_t = double (*f) (double, Ts...); 

trên định nghĩa một custom_function_t mà phải mất một biến đôi và số lượng đối số.

nên chức năng trap của bạn trở nên

template< typename... Ts > 
double trap(custom_function_t<Ts...> f, Ts... args, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
     double xi = a + i*step; 
     if (i == 0 || i == N) { s += f(xi, args...); } 
     else { s += 2*f(xi, args...); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Cách sử dụng:

double foo (double X) { 
    return X; 
} 

double quad(double X, double A, double B, double C) { 
    return(A*pow(x,2) + B*x + C); 
} 

int main() { 
    double result_foo = trap(foo, 0, 1); 
    double result_quad = trap(quad, 1, 2, 3, 0, 1); // 1, 2, 3 == A, B, C respectively 
} 

Tested trên Apple LLVM 4.2 trình biên dịch.

+0

Đó chỉ là khó hiểu –

+0

@MooingDuck Tôi đồng ý ... – yngccc

Các vấn đề liên quan