2013-04-11 204 views
8

長時間瀏覽器,這裏首次提問。我寫了很多腳本來完成各種1D數值積分方法,並將它們編譯成一個庫。我希望圖書館儘可能靈活地考慮其能夠整合的內容。C++:以任意數量參數作爲參數傳遞函數

這裏我舉一個例子:一個非常簡單的梯形法則示例,其中我將一個指針傳遞給要集成的函數。

// 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; 
} 

這適用於只有一個參數的簡單函數。例如:

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

但是,有時我可能想要集成更多參數的東西,如二次多項式。在這個例子中,係數將在整合之前由用戶定義。示例代碼:

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

理想情況下,我將能夠做這樣的事情來整合它:

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

但顯然不起作用。我已經通過定義具有係數作爲成員的利益爲成員函數的函數的類解決此問題得到:

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); } 
}; 

然而,然後我的積分功能需要改變把對象作爲輸入,而不是一個函數指針:

// 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; 
} 

這工作得很好,但由此產生的圖書館是不是很獨立的,因爲它需要在某處被定義的類模型。此外,理想情況下,模型應該能夠從用戶到用戶改變,所以我不想修復它在一個頭文件。我試圖使用函數模板和函數來獲得這個工作,但它不是很獨立,因爲模板應該在頭文件中定義(除非你想顯式實例化,我不這樣做)。總結:有沒有什麼辦法可以讓我的集成函數接受具有可變數量輸入參數的任意1D函數,同時仍然保持足夠的獨立性,以便可以將它們編譯爲獨立的庫?在此先感謝您的建議。

+0

['std :: bind'](http://en.cppreference.com/w/cpp/utility/functional/bind) – 2013-04-11 23:21:09

回答

8

您需要的是模板和std::bind()(或其對應的boost::bind(),如果您買不起C++ 11)。舉例來說,這是你的trap()功能會變成什麼樣子:

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; 
} 

請注意,我們從函數指針推廣,並允許任何類型可調用的對象的(包括C++ 11的λ,爲實例)因此,調用用戶提供的函數的語法不是*f(param)(僅適用於函數指針),而只是f(param)

關於靈活性,讓我們考慮兩個硬編碼功能(並假裝他們是有意義的):

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

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

現在你可以直接輸入到trap()同時提供了第一個功能,或者結合的最後結果第二個函數的一些特定值的三個參數(你有哪些參數綁定自由選擇):

#include <functional> 

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

當然,你可以用lambda表達式獲得更大的靈活性:

#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 
} 

而且你還可以通過自己的狀態函子TP trap(),或包裹在一個std::function對象一些可調用的對象(或boost::function,如果您不能負擔C++ 11)。選擇很廣泛。

這是live example

+0

謝謝!我喜歡模板的例子,因爲它非常優雅,但我不確定它會對我有用 - 我的理解是模板函數必須在同一個地方定義和聲明,而我想定義'trap( )'在一個單獨的庫中,然後聲明它用於其他項目。我把你的建議與'std :: bind'一起使用'std :: function',並將'trap()'重新定義爲'雙重陷阱(std :: function f,double a,double b )';唯一令人討厭的部分是我必須重新定義簡單的函數,如sin作爲'std :: function'。 – t354 2013-04-12 14:19:11

+2

@ t354:是的,模板受到這種分離問題的困擾:定義必須位於頭文件中。或者,您可以*將定義放在'.cpp'文件中,併爲您的應用程序中使用的所有可能參數提供模板的所謂*顯式實例*,但如果您的應用程序是庫,則無法預測你的模板將如何實例化。把'std :: function'放在簽名中是OK的:要知道有一個運行時間的開銷,所以你必須測量它是否對你有用。 – 2013-04-12 14:27:49

+1

@ t354:我不明白最後一部分:你是什麼意思「重新定義簡單的函數,如'罪'」?如果你的意思是這樣的話:'std :: function = sin;陷阱(罪,0,42);',而不是不需要。你可以直接調用'trap(sin,0,42);',而不用聲明std :: function'對象。 – 2013-04-12 14:28:59

3

你所要做的就是讓這一切成爲可能

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

用C++ 11我們有別名模板和可變參數模板

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

上述定義custom_function_t是採取雙重和可變參數數量。

所以你trap功能變得

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; 
} 

用法:

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 
} 

測試蘋果LLVM編譯器4.2。

+0

這只是令人困惑 – 2013-04-11 23:22:44

+0

@MooingDuck我同意... – yngccc 2013-04-11 23:31:42