3

フェルミ気体用のツールを実装する C++ で共有ライブラリを作成しようとしています。GSL ライブラリを使用して関数を数値的に解いており、コードをスクリプトとして実行しているときは問題なく実行されますが、共有ライブラリとクラスに変換しようとすると問題が発生します。

同様の質問を見てきました: Q1 Q2 Q3

私はC++プログラミングにかなり慣れていないので、私の問題に対するさまざまな答えを適応させることができないようです. おそらく、私は答えをよく理解していないからです。

私のコードは次のとおりです。

/* Define structure for the GSL-function: chempot_integrand */
struct chempot_integrand_params { double mu; double T; };

double
ChemicalPotential::chempot_integrand (double x, void * params){
    /* Computes the integrand for the integral used to obtain the chemical potential.
     *
     * This is a GSL-function, which are integrated using gsl_integration_qag.
     */

    // Get input parameters.
    struct chempot_integrand_params * p = (struct chempot_integrand_params *) params;
    double mu = p->mu;
    double T = p->T;

    // Initiate output parameters for GSL-function.
    gsl_sf_result_e10 result;
    int status = gsl_sf_exp_e10_e( ( gsl_pow_2(x) - mu ) / T , &result );

    if (status != GSL_SUCCESS){
        printf ("Fault in calculating exponential function.");
    }

    // Return (double) integrand.
    return (gsl_pow_2(x) / ( 1 + result.val * gsl_sf_pow_int(10,result.e10) ));
}

/* Define structure for the GSL-function: chempot_integration */
struct chempot_integral_params { double T; };

double
ChemicalPotential::chempot_integration (double mu, double T){
    /* Computes the integral used to obtain the chemical potential using the integrand: chempot_integrand.
    */

    // Set input parameters for the integrand: chempot_integrand.
    struct chempot_integrand_params params_integrand = { mu, T };

    // Initiate the numerical integration.
    gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000); // Allocate memory for the numerical integration. Can be made larger if neccessary, REMEMBER to change it in the function call: gsl_integration_qag as well.
    double result, error;
    gsl_function F;
    F.function = &ChemicalPotential::chempot_integrand;
    F.params = &params_integrand;

    // Upper limit for integration
    double TOL = 1e-9;
    double upp_lim = - T * gsl_sf_log(TOL) + 10;

    gsl_integration_qag (&F, 0, upp_lim, 1e-12, 1e-12, 1000, 6, w, &result, &error);

    // Free memory used for the integration.
    gsl_integration_workspace_free (w);

    return result;
}

コンパイル時にエラーが発生します

error: cannot convert ‘double (Fermi_Gas::ChemicalPotential::*)(double, void*)’ to ‘double (*)(double, void*)’ 

列をなして

F.function = &ChemicalPotential::chempot_integrand;
4

2 に答える 2

3

人々がこれを何度も何度も尋ねるのは、実に興味深いことです。理由の 1 つは、提案されたソリューションが理解しにくいことです。私は、それらを理解して実装するのに問題がありました。(ご想像のとおり、ソリューションはそのままでは機能しませんでした。)

tlamadonの助けを借りて、ここでも役立つ可能性のある解決策を見つけました。皆さんの考えを見てみましょう。

要約すると、問題は、GSL ライブラリーの何かを操作したいメンバー関数を含むクラスがあることです。この例は、GSL インターフェースが

gsl_function F;

定義については、こちらを参照してください。

クラスの例は次のとおりです。

class MyClass {

    private:
        gsl_f_pars *p;  // not necessary to have as member

    public: 
        double obj(double x, void * pars);  // objective fun
        double GetSolution( void );  
        void setPars( gsl_f_pars * xp ) { p = xp; };
        double getC( void )  ;  // helper fun

};

この演習の目的は、次のことができるようになることです。

  1. 開始MyClass test,
  2. パラメータ struct を指定して (または対応するコンストラクタを記述して)、
  3. それを呼び出すtest.GetSolution()と、GSL関数が使用されたものは何でも返されます( の最小値obj、ルート、積分など)

トリックは、MyClass へのポインターstruct gsl_f_parsであるパラメーターに要素を入れることです。構造体は次のとおりです。

struct gsl_f_pars {
    double a;
    double b;
    double c;
    MyClass * pt_MyClass;
};

最後の部分は、内部で呼び出されるラッパーを提供することですMyClass::GetSolution()(ラッパーはメンバー関数の代わりであり、クラス内でMyClass::obj単に指すことはできません)。&objこのラッパーは、パラメータ struct、dereference pt_MyClass、および evaluatept_MyClassの memberを受け取りobjます。

// Wrapper that points to member function
// Trick: MyClass is an element of the gsl_f_pars struct
// so we can tease the value of the objective function out
// of there.
double gslClassWrapper(double x, void * pp) {
    gsl_f_pars *p = (gsl_f_pars *)pp;
    return p->pt_MyClass->obj(x,p);
}

完全な例をここに投稿するには少し長すぎるので、要旨を掲載します。これはヘッダー ファイルcpp ファイルであり、GSL があればどこでも動作するはずです。コンパイルして実行する

g++ MyClass.cpp -lgsl -o test
./test
于 2013-10-22T16:30:03.420 に答える
1

これは重複した質問です。たとえば、Q1またはQ2を参照してください。問題は次のとおりです。メンバー関数へのポインターをフリー関数ポインターに変換することはできません。問題を解決するには、2 つのオプションがあります。メンバー関数を静的として定義することができます (メンバー関数はクラスのインスタンス化にアタッチされず、それがフリー関数に変換できるため、ケースの 90% で悪いことです)。リンクしたラッパーは、内部で静的メンバー関数を使用して、特定のメンバー関数を静的に宣言する必要なく、コードを gsl と互換性を持たせます。

EDIT @Florian Oswald。基本的に、前に引用したラッパー std::bind を使用して、ソリューション全体を2行で実装できます

gsl_function_pp Fp( std::bind(&Class::member_function, &(*this),  std::placeholders::_1) );
gsl_function *F = static_cast<gsl_function*>(&Fp);

実際には、これは純粋な C コードから 1 行追加するだけです。

コメントで述べたように、追加のグローバル構造体と追加のグローバル関数を使用して統合するすべてのメンバー関数をラップするのは面倒であり、完全に不要な多くの追加の関数/構造体でコードを汚染します。(C と比較して) C++ を強力かつ便利にする機能の使用を拒否する場合、なぜ c++ を使用するのでしょうか?

別の古典的な例: たくさんのパラメーターを渡したい場合は、ラムダ関数を使用してください (余分な構造体やグローバル関数は使用しません) !!!

より正確に言うと、2 つのパラメーター (double) があるとします。

 //Declare them (locally) here
   double a1  = ...;
   double a2  = ...;
 // Declare a lambda function that capture all of them by value or reference
 // no need to write another struct with these 2 parameters + class pointer
   auto ptr = [&](double x)->double {/.../};
 // Cast to GSL in 3 lines using the wrapper 
 std::function<double(double)> F1(ptr);
 gsl_function_pp F2(F1);
 gsl_function *F = static_cast<gsl_function*>(&F2); 

グローバル関数の余分なグローバル構造体や余分なラッパーはありません (メンバー関数の統合の問題を解決した同じラッパーが、ラムダ式の統合の問題も解決しました)。もちろん、これは最終的にはスタイルの問題ですが、コードを肥大化させることなく C ライブラリを使用できるようにするこれらの優れた機能がなければ、私は C から離れることはありません。

于 2013-10-18T18:00:43.227 に答える