3

operator()ある点で数学関数の値を返す関数を定義する一般的な C++ クラスを設計したいと考えています。operator()ただし、既に定義されているオブジェクト、またはクラスが補間して値を与える値のテーブルを使用して、そのクラスをインスタンス化できるようにしたいと考えています。次のアプローチを考えました。

class MathFunction
{
    MathFunction(Functor& function);
    MathFunction(Mat<double> interpolationValues);

    double operator()(double radius, double angle);
};

コンストラクターはメンバー変数を設定double operator()でき、switch ステートメントを使用して、必要な出力方法を決定できますdouble。ただし、これは非常にエレガントではないようです。

このクラスのテンプレート化されたバージョンは役に立つかもしれませんが、2 つのメソッドはコードを共有していないので、それを行うのが賢明でしょうか? そして、テンプレート化されたバージョンはどのように正確に設計されますか?

私がはっきりしているかどうかはわかりません。説明が必要な場合はコメントしてください。

編集:私はその完全性のために Useless の回答を選択しましたが、dasblikenlight の貢献の速度と明快さを強調したいと思います。ありがとう。

4

5 に答える 5

6

これは、戦略パターンを使用する主なケースです。コンストラクターに渡されるパラメーターに応じて、 に依存する戦略オブジェクト、または補間された値のリストに依存するMathFunction戦略オブジェクトのいずれかをインスタンス化する必要があります。Functorの呼び出しはoperator ()、呼び出しを戦略に転送し、共通の仮想関数の実装を通じて結果を取得する必要があります。

class MathFunction {
    struct Strategy {
        virtual double Calculate(double radius, double angle)=0;
        virtual ~Strategy(){}
    };
    class FunctorStrategy : public Strategy {
        Functor& _function;
    public:
        FunctorStrategy(Functor& function)  : _function(function) {}
        virtual double Calculate(double radius, double angle) {
            return _function(radius, angle);
        }
    }
    class InterpolationStrategy : public Strategy {
        Mat<double> _interpolationValues;
    public:
        InterpolationStrategy (Mat<double> interpolationValues) 
        : _interpolationValues(interpolationValues) {}
        virtual double Calculate(double radius, double angle) {
            return ...; // Use _interpolationValues to do calculation
        }
    };
    unique_ptr<Strategy> _strategy;
public:
    MathFunction(Functor& function)
    : _strategy(new FunctorStrategy(function)) {}
    MathFunction(Mat<double> interpolationValues)
    : _strategy(new InterpolationStrategy(interpolationValues)) {}
    // And now for the complicated part:
    double operator()(double radius, double angle) {
        return _strategy->Calculate(radius, angle); // TA-DA!!!
    }
};
于 2012-12-14T15:49:59.080 に答える
1

MathFunction共通の実装がない場合、それは何のためですか?

その目的が 2 つの実装に共通のインターフェイスを提供することである場合は、それをどこで使用するかを検討してください。

  1. テンプレート化されていない (または非依存の) コードで、実行時に実装タイプが合理的に変化する可能性がある場合
  2. の型に合理的に依存する可能性のあるテンプレート化さMathFunctionれたコード、または使用している型を常に静的に認識しているコード

ケース 1 では、戦略パターンのようなものを使用するのが合理的です。MathFunction仮想戦略にディスパッチする静的に型指定されたラッパーにするか、抽象 MathFunction 基本クラスを記述して、2 つの具象サブクラスのいずれかをインスタンス化することができます。

dasblinkenlightはすでに Strategy バージョンのデモを行っているので、これに相当する ABC を次に示します (ファクトリ関数を使用して実装の詳細を非表示にする方法に注意してください)。

// public header
class MathFunction {
public:
    virtual ~MathFunction() {}
    virtual double operator() (double radius, double angle) = 0;
};

MathFunction* make_function(Functor& function);
MathFunction* make_function(Mat<double> interpolationValues);

// implementation
class FunctorMathFunction : public MathFunction {
    Functor& _function;
public:
    FunctorMathFunction(Functor& function) : _function(function) {}
    virtual double operator() (double radius, double angle) {
        return _function(radius, angle);
    }
};
class TableMathFunction : public MathFunction {
    Mat<double> _interpolationValues;
public:
    TableMathFunction (Mat<double> interpolationValues) 
    : _interpolationValues(interpolationValues) {}
    virtual double operator() (double radius, double angle) {
        return ...; // Use _interpolationValues to do calculation
    }
};

MathFunction* make_function(Functor& function) {
    return new FunctorMathFunction(function);
}
MathFunction* (Mat<double> interpolationValues) {
    return new TableMathFunction(interpolationValues);
}

ケース 2 では、テンプレート化MathFunctionが適切な選択です。それを (部分的に) 特殊化するか、評価をデリゲートする Policy クラスで MathFunction をパラメーター化することである Strategy パターンに相当するコンパイル時を使用することができます。パフォーマンスが問題になる場合は、仮想関数呼び出しを保存する (およびインライン化を有効にする) と役立つ場合があります。

template <typename EvaluationPolicy>
class MathFunction
{
    EvaluationPolicy eval;
public:
    MathFunction() {}
    MathFunction(EvaluationPolicy const &ep) : eval(ep) {}

    double operator() (double radius, double angle) {
        return eval.calc(radius, angle);
    }
};

class FunctionPolicy
{
    std::function<double(double,double)> func;
public:
    FunctionPolicy(std::function<double(double,double)> f) : func(f) {}
    double calc(double r, double a) { return func(r, a); }
};

class TablePolicy
{
    Mat<double> mat;
public:
    TablePolicy(Mat<double> values) : mat(values) {}
    double calc(double r, double a) { return ....; }
};

FunctionMathFunction2 つの別個の具象クラス (および)を記述するだけでなくポリシーを使用する利点は、それらの関係を示すこと、ポリシーに依存しないTableMathFunctionコードを共有すること、および関連のない問題を個別のポリシーに分割することができることです。

于 2012-12-14T16:02:49.643 に答える
1

しかし、2 つのメソッドはコードを共有していないので、共有するのが賢明でしょうか?

operator() を介して出力を生成する方法が両方のメカニズムを介して相乗的でない場合、それらを 1 つのクラスに入れることはあまり良い考えではないと思います。これは、Single Responsibility Principle ( SRP ) に違反していると言えます。

代わりに、ストラテジー デザイン パターンを使用して、機能を 2 つの個別のストラテジー クラスに分散してみてください。その後、クラスを戦略でテンプレート化できます

于 2012-12-14T15:49:02.047 に答える
0

あなたのコードのテンプレート化されたバージョンが本当にデザインの改善であるかどうかはわかりません。あなたが指摘したように、主な問題は、ここで 2 つの完全な異なるメカニズムが動作していて、それらが 1 つのクラスにまとめられていることです。テンプレートで機能させる 1 つの方法は、基本的に 2 つの特殊化されたテンプレートを作成することです。1 つは関数を受け取るケースの特殊化であり、もう 1 つはテーブルを受け取るケースです。ただし、これが正しいメカニズムであるとは確信していません。

ここで仮想関数呼び出しのオーバーヘッドを絶対に許容できないことをプロファイリングによって確認できない限り、コンパイル時のポリモーフィズムではなく実行時ポリモーフィズムを使用し、MathFunctionクラスを純粋な仮想を提供するインターフェイスに変換してoperator()から、2 つの実装を派生させます。 1 つは関数のケースを処理し、もう 1 つはテーブルのケースを処理します。

于 2012-12-14T15:51:55.987 に答える
0

ここでポリモーフィズムを使用できます。

class MathFunction
{
private:

    class Impl
    {
    public:

       virtual ~Impl() {}
       virtual double compute(double radius, double angle) = 0;
    };

    class FunctorImpl : public Impl
    {
    public:

       explicit FunctorImpl(Functor & function);
       double compute(double radius, double angle);
    };

    class MatImpl : public Impl
    {
       public:

       explicit MatImpl( Mat<double> interpolationValues);
       double compute(double radius, double angle);
    };

    std::unique_ptr< Impl > impl;

public:

    MathFunction(Functor& function) : impl( new FunctorImpl( function ) ) {}
    MathFunction(Mat<double> interpolationValues) : impl( new MatImpl( interpolationValues ) ) {}

    double operator()(double radius, double angle)
    {
       return impl->compute( radius, angle );
    }
};
于 2012-12-14T15:56:40.680 に答える