通常情況下,需要調(diào)用由用戶提供的函數(shù)的算法是難以實(shí)現(xiàn)重用的。而實(shí)現(xiàn)重用的關(guān)鍵就在于尋找一種封裝用戶定義代碼的有效途徑。
引言
“代碼重用”是軟件工程追求的神圣目標(biāo)之一。采用面向?qū)ο螅╫bject-oriented, OO)的程序設(shè)計(jì)方法的一個(gè)主要方面也就是為了代碼重用,這可以從任何介紹OO程序設(shè)計(jì)的書籍看得出來(lái)。然而實(shí)際應(yīng)用中,使用C++一類的OO語(yǔ)言來(lái)實(shí)現(xiàn)代碼重用比我們想象的要難得多。事實(shí)上,正如一位作者所說(shuō),由于C++程序員普遍傾向于創(chuàng)建自己的容器類,“C++對(duì)科學(xué)計(jì)算軟件的可重用性造成了很大的阻礙”。
在本文中,我展示了怎樣用C++語(yǔ)言創(chuàng)建可重用的數(shù)學(xué)例程。相對(duì)于OO來(lái)說(shuō),我使用的方法更依賴于通用編程(Generic Programming)。為了討論的方便,我使用了一個(gè)廣泛應(yīng)用的估計(jì)算法——Newton-Raphson算法來(lái)作為例子。Newton-Raphson算法必須調(diào)用一個(gè)用戶定義的函數(shù)。本文首先給出了用戶定義函數(shù)的典型(并不讓人滿意)封裝方法,然后提出了一種建立在模板和操作符重載基礎(chǔ)上的更加令人滿意的封裝方式。
Newton-Raphson算法
在科學(xué)計(jì)算和財(cái)經(jīng)工程領(lǐng)域,許多數(shù)值算法都是通用的(至少在理論上是),可廣泛地用于解決一類問(wèn)題。一個(gè)大家熟悉的例子就是Newton-Raphson例程,它可用來(lái)尋找方程f(x)=0的數(shù)值解。標(biāo)準(zhǔn)的數(shù)學(xué)表達(dá)式f(x)表示f是變量x的函數(shù),其通常的表達(dá)形式為f(x,a,b,...)=0,f被定義為多于一個(gè)變量的函數(shù)。在這種情況下,Newton-Raphson算法試圖把x以外的變量固定并作為參數(shù),而尋找關(guān)于變量x的數(shù)值解。
由于Newton-Raphson算法需要知道被求解函數(shù)的確切表達(dá),其傳統(tǒng)實(shí)現(xiàn)方法是直接將代碼嵌入到客戶應(yīng)用程序中。這就使得算法的實(shí)現(xiàn)代碼經(jīng)過(guò)針對(duì)不同被求解函數(shù)的少量修改后在客戶程序中反復(fù)出現(xiàn)。
同許多其它數(shù)學(xué)例程一樣,Newton-Raphson算法的具體實(shí)現(xiàn)是應(yīng)該與特定用戶無(wú)關(guān)的。并且,重復(fù)編碼在任何情況下都應(yīng)該盡量避免。我們很自然地會(huì)想到把該類例程作為庫(kù)函數(shù)來(lái)實(shí)現(xiàn),以使客戶程序可以直接調(diào)用它們。但是,這種實(shí)現(xiàn)方式必然會(huì)涉及到如何將用戶自定義函數(shù)(Newton-Raphson例程需要調(diào)用該函數(shù))封裝成可以作為參數(shù)傳遞的形式。下面部分描述了一種通常的,也是存在很多問(wèn)題的用戶定義函數(shù)封裝方法。
通常的實(shí)現(xiàn)途徑——函數(shù)指針
現(xiàn)在的任務(wù)就是把Newton-Raphson算法作為一個(gè)庫(kù)例程來(lái)實(shí)現(xiàn),客戶程序可以直接調(diào)用該例程來(lái)對(duì)任何形如f(x,a,b,..)=0的方程求取關(guān)于x的數(shù)值解。問(wèn)題的關(guān)鍵就是算法的實(shí)現(xiàn)必須使用(能夠調(diào)用)f(x,a,b,...)形式的通用函數(shù),而該函數(shù)的具體定義由庫(kù)的用戶在以后提供,并且只能在運(yùn)行時(shí)才提交給庫(kù)。對(duì)于C和C++程序員,一種自然的可能方式就是把函數(shù)指針作為參數(shù)傳遞給庫(kù)例程:
typedef double (*P2F)(double);
double NewtonRaphson(P2F func_of_x, double x_init,) {
...
//通過(guò)函數(shù)指針調(diào)用函數(shù)
double y = func_of_x( x_init );
...
}
該庫(kù)例程工作得很好,但這僅僅是對(duì)于恰好只有一個(gè)參數(shù)的函數(shù)來(lái)說(shuō)的。在C++中,程序員可以對(duì)庫(kù)函數(shù)進(jìn)行重載,為具有不同參數(shù)數(shù)目的用戶定義函數(shù)分別定義一個(gè)例程。但是這樣會(huì)使得庫(kù)代碼出現(xiàn)大量的重復(fù),并且更為糟糕的是,你不知道到底需要定義多少個(gè)這樣的庫(kù)例程。
另一種想法就是利用可選參數(shù),如下面語(yǔ)句所示:
typedef double (*P2F)(double, ...);
這似乎看來(lái)可以結(jié)束這個(gè)問(wèn)題的討論了。但是幸運(yùn)也不幸運(yùn)的是,C++不允許如上面代碼所期望的那樣使用可選參數(shù)。由于指向函數(shù)的指針必須準(zhǔn)確地知道函數(shù)參數(shù)的類型和個(gè)數(shù),該typedef定義的函數(shù)指針就只能與有一個(gè)double類型參數(shù)并跟上C風(fēng)格的varargs的函數(shù)匹配,而不能用于包含了更多指定類型參數(shù)的函數(shù)。
當(dāng)然還有其它的傳遞多參數(shù)函數(shù)的途徑,比如說(shuō)可使用函數(shù)外殼。但是這種方法對(duì)于作者來(lái)說(shuō),除了求助于全局變量以外,并不清楚該怎樣去做。
為使其簡(jiǎn)化,就需要使用一組包含了一定參數(shù)的構(gòu)造,這些構(gòu)造定義了復(fù)雜的用戶函數(shù),并為庫(kù)例程通過(guò)傳遞單個(gè)參數(shù)來(lái)調(diào)用這個(gè)函數(shù)提供了途徑。這就將是一個(gè)對(duì)象——一個(gè)純粹并簡(jiǎn)單的對(duì)象。因此,我為通用函數(shù)f(x,a,b,...)定義了一個(gè)類,并將其命名為FuncObj。(為了簡(jiǎn)化敘述,從現(xiàn)在開始,參數(shù)的個(gè)數(shù)被固定為3個(gè)。)
class FuncObj {
private:
double _a;
int _b;
public:
FuncObj(double a_in, int b_in);
// 用x, a, b的形式定義用戶定義函數(shù)
double theFunc(double x_init);
};
你可能試圖通過(guò)向先前定義的庫(kù)例程傳遞一個(gè)指向FuncObj對(duì)象的theFunc成員函數(shù)的指針來(lái)調(diào)用該例程。但是這種方法不能工作,至少因?yàn)閮牲c(diǎn)原因。首先,在成員函數(shù)的表示中包含有類的名稱,指向它的指針不能用于需要一個(gè)指向普通函數(shù)的指針的地方。其次,指向成員函數(shù)的指針必須通過(guò)一個(gè)該類的對(duì)象實(shí)例來(lái)存取。我將在下一個(gè)部分解決這兩個(gè)問(wèn)題。(需要注意的是,把theFunc定義成static類型無(wú)法真正解決問(wèn)題,因?yàn)檫@樣的話,theFunc就不能存取FuncObj的非靜態(tài)成員變量,而正是這些成員變量保存了運(yùn)算所需的其它“常值”變量。)
引言
“代碼重用”是軟件工程追求的神圣目標(biāo)之一。采用面向?qū)ο螅╫bject-oriented, OO)的程序設(shè)計(jì)方法的一個(gè)主要方面也就是為了代碼重用,這可以從任何介紹OO程序設(shè)計(jì)的書籍看得出來(lái)。然而實(shí)際應(yīng)用中,使用C++一類的OO語(yǔ)言來(lái)實(shí)現(xiàn)代碼重用比我們想象的要難得多。事實(shí)上,正如一位作者所說(shuō),由于C++程序員普遍傾向于創(chuàng)建自己的容器類,“C++對(duì)科學(xué)計(jì)算軟件的可重用性造成了很大的阻礙”。
在本文中,我展示了怎樣用C++語(yǔ)言創(chuàng)建可重用的數(shù)學(xué)例程。相對(duì)于OO來(lái)說(shuō),我使用的方法更依賴于通用編程(Generic Programming)。為了討論的方便,我使用了一個(gè)廣泛應(yīng)用的估計(jì)算法——Newton-Raphson算法來(lái)作為例子。Newton-Raphson算法必須調(diào)用一個(gè)用戶定義的函數(shù)。本文首先給出了用戶定義函數(shù)的典型(并不讓人滿意)封裝方法,然后提出了一種建立在模板和操作符重載基礎(chǔ)上的更加令人滿意的封裝方式。
Newton-Raphson算法
在科學(xué)計(jì)算和財(cái)經(jīng)工程領(lǐng)域,許多數(shù)值算法都是通用的(至少在理論上是),可廣泛地用于解決一類問(wèn)題。一個(gè)大家熟悉的例子就是Newton-Raphson例程,它可用來(lái)尋找方程f(x)=0的數(shù)值解。標(biāo)準(zhǔn)的數(shù)學(xué)表達(dá)式f(x)表示f是變量x的函數(shù),其通常的表達(dá)形式為f(x,a,b,...)=0,f被定義為多于一個(gè)變量的函數(shù)。在這種情況下,Newton-Raphson算法試圖把x以外的變量固定并作為參數(shù),而尋找關(guān)于變量x的數(shù)值解。
由于Newton-Raphson算法需要知道被求解函數(shù)的確切表達(dá),其傳統(tǒng)實(shí)現(xiàn)方法是直接將代碼嵌入到客戶應(yīng)用程序中。這就使得算法的實(shí)現(xiàn)代碼經(jīng)過(guò)針對(duì)不同被求解函數(shù)的少量修改后在客戶程序中反復(fù)出現(xiàn)。
同許多其它數(shù)學(xué)例程一樣,Newton-Raphson算法的具體實(shí)現(xiàn)是應(yīng)該與特定用戶無(wú)關(guān)的。并且,重復(fù)編碼在任何情況下都應(yīng)該盡量避免。我們很自然地會(huì)想到把該類例程作為庫(kù)函數(shù)來(lái)實(shí)現(xiàn),以使客戶程序可以直接調(diào)用它們。但是,這種實(shí)現(xiàn)方式必然會(huì)涉及到如何將用戶自定義函數(shù)(Newton-Raphson例程需要調(diào)用該函數(shù))封裝成可以作為參數(shù)傳遞的形式。下面部分描述了一種通常的,也是存在很多問(wèn)題的用戶定義函數(shù)封裝方法。
通常的實(shí)現(xiàn)途徑——函數(shù)指針
現(xiàn)在的任務(wù)就是把Newton-Raphson算法作為一個(gè)庫(kù)例程來(lái)實(shí)現(xiàn),客戶程序可以直接調(diào)用該例程來(lái)對(duì)任何形如f(x,a,b,..)=0的方程求取關(guān)于x的數(shù)值解。問(wèn)題的關(guān)鍵就是算法的實(shí)現(xiàn)必須使用(能夠調(diào)用)f(x,a,b,...)形式的通用函數(shù),而該函數(shù)的具體定義由庫(kù)的用戶在以后提供,并且只能在運(yùn)行時(shí)才提交給庫(kù)。對(duì)于C和C++程序員,一種自然的可能方式就是把函數(shù)指針作為參數(shù)傳遞給庫(kù)例程:
typedef double (*P2F)(double);
double NewtonRaphson(P2F func_of_x, double x_init,) {
...
//通過(guò)函數(shù)指針調(diào)用函數(shù)
double y = func_of_x( x_init );
...
}
該庫(kù)例程工作得很好,但這僅僅是對(duì)于恰好只有一個(gè)參數(shù)的函數(shù)來(lái)說(shuō)的。在C++中,程序員可以對(duì)庫(kù)函數(shù)進(jìn)行重載,為具有不同參數(shù)數(shù)目的用戶定義函數(shù)分別定義一個(gè)例程。但是這樣會(huì)使得庫(kù)代碼出現(xiàn)大量的重復(fù),并且更為糟糕的是,你不知道到底需要定義多少個(gè)這樣的庫(kù)例程。
另一種想法就是利用可選參數(shù),如下面語(yǔ)句所示:
typedef double (*P2F)(double, ...);
這似乎看來(lái)可以結(jié)束這個(gè)問(wèn)題的討論了。但是幸運(yùn)也不幸運(yùn)的是,C++不允許如上面代碼所期望的那樣使用可選參數(shù)。由于指向函數(shù)的指針必須準(zhǔn)確地知道函數(shù)參數(shù)的類型和個(gè)數(shù),該typedef定義的函數(shù)指針就只能與有一個(gè)double類型參數(shù)并跟上C風(fēng)格的varargs的函數(shù)匹配,而不能用于包含了更多指定類型參數(shù)的函數(shù)。
當(dāng)然還有其它的傳遞多參數(shù)函數(shù)的途徑,比如說(shuō)可使用函數(shù)外殼。但是這種方法對(duì)于作者來(lái)說(shuō),除了求助于全局變量以外,并不清楚該怎樣去做。
為使其簡(jiǎn)化,就需要使用一組包含了一定參數(shù)的構(gòu)造,這些構(gòu)造定義了復(fù)雜的用戶函數(shù),并為庫(kù)例程通過(guò)傳遞單個(gè)參數(shù)來(lái)調(diào)用這個(gè)函數(shù)提供了途徑。這就將是一個(gè)對(duì)象——一個(gè)純粹并簡(jiǎn)單的對(duì)象。因此,我為通用函數(shù)f(x,a,b,...)定義了一個(gè)類,并將其命名為FuncObj。(為了簡(jiǎn)化敘述,從現(xiàn)在開始,參數(shù)的個(gè)數(shù)被固定為3個(gè)。)
class FuncObj {
private:
double _a;
int _b;
public:
FuncObj(double a_in, int b_in);
// 用x, a, b的形式定義用戶定義函數(shù)
double theFunc(double x_init);
};
你可能試圖通過(guò)向先前定義的庫(kù)例程傳遞一個(gè)指向FuncObj對(duì)象的theFunc成員函數(shù)的指針來(lái)調(diào)用該例程。但是這種方法不能工作,至少因?yàn)閮牲c(diǎn)原因。首先,在成員函數(shù)的表示中包含有類的名稱,指向它的指針不能用于需要一個(gè)指向普通函數(shù)的指針的地方。其次,指向成員函數(shù)的指針必須通過(guò)一個(gè)該類的對(duì)象實(shí)例來(lái)存取。我將在下一個(gè)部分解決這兩個(gè)問(wèn)題。(需要注意的是,把theFunc定義成static類型無(wú)法真正解決問(wèn)題,因?yàn)檫@樣的話,theFunc就不能存取FuncObj的非靜態(tài)成員變量,而正是這些成員變量保存了運(yùn)算所需的其它“常值”變量。)