1

2つの派生クラスで共有される計算をキャッシュする方法についてアドバイスをお願いします。例として、2種類の正規化ベクトルL1とL2があり、それぞれが独自の正規化定数を定義しています(注:std::vectorここから簡単な例として継承している良い習慣に反して、信じられないかもしれませんが、私の本当の問題はL1およびL2ベクトルについて!):

#include <vector>
#include <iostream>
#include <iterator>
#include <math.h>

struct NormalizedVector : public std::vector<double> {
  NormalizedVector(std::initializer_list<double> init_list):
    std::vector<double>(init_list) { }
  double get_value(int i) const {
    return (*this)[i] / get_normalization_constant();
  }
  virtual double get_normalization_constant() const = 0;
};

struct L1Vector : public NormalizedVector {
  L1Vector(std::initializer_list<double> init_list):
    NormalizedVector(init_list) { }
  double get_normalization_constant() const {
    double tot = 0.0;
    for (int k=0; k<size(); ++k)
      tot += (*this)[k];
    return tot;
  }
};

struct L2Vector : public NormalizedVector {
  L2Vector(std::initializer_list<double> init_list):
    NormalizedVector(init_list) { }
  double get_normalization_constant() const {
    double tot = 0.0;
    for (int k=0; k<size(); ++k) {
      double val = (*this)[k];
      tot += val * val;
    }
    return sqrt(tot);
  }
};

int main() {
  L1Vector vec{0.25, 0.5, 1.0};
  std::cout << "L1 ";
  for (int k=0; k<vec.size(); ++k)
    std::cout << vec.get_value(k) << " ";
  std::cout << std::endl;

  std::cout << "L2 ";
  L2Vector vec2{0.25, 0.5, 1.0};
  for (int k=0; k<vec2.size(); ++k)
    std::cout << vec2.get_value(k) << " ";
  std::cout << std::endl;
  return 0;
}

get_normalization_constant()このコードは、構築後に変更されない場合でも、繰り返し呼び出されるため、大きなベクトルの場合は不必要に遅くなります(などの修飾子push_backが適切に無効になっていると仮定します)。

正規化の1つの形式のみを検討している場合は、単純にdouble値を使用して、構築時にこの結果をキャッシュします。

struct NormalizedVector : public std::vector<double> {
  NormalizedVector(std::initializer_list<double> init_list):
    std::vector<double>(init_list) {
    normalization_constant = get_normalization_constant();
  }
  double get_value(int i) const {
    return (*this)[i] / normalization_constant;
  }

  virtual double get_normalization_constant() const = 0;
  double normalization_constant;
};

NormalizedVectorただし、コンストラクターが純粋仮想関数を呼び出そうとするため、これは当然のことながらコンパイルされません(派生仮想テーブルはベースの初期化中に使用できません)。


オプション1: 派生クラスはnormalization_constant = get_normalization_constant();、コンストラクターで関数を手動で呼び出す必要があります。


オプション2: オブジェクトは、定数を初期化するための仮想関数を定義します。

init_normalization_constant() {
  normalization_constant = get_normalization_constant();
}

次に、オブジェクトはファクトリによって構築されます。

struct NormalizedVector : public std::vector<double> {
  NormalizedVector(std::initializer_list<double> init_list):
    std::vector<double>(init_list) {
    //    init_normalization_constant();
  }
  double get_value(int i) const {
    return (*this)[i] / normalization_constant;
  }

  virtual double get_normalization_constant() const = 0;
  virtual void init_normalization_constant() {
    normalization_constant = get_normalization_constant();
  }

  double normalization_constant;
};

// ...
// same code for derived types here
// ...

template <typename TYPE>
struct Factory {
  template <typename ...ARGTYPES>
  static TYPE construct_and_init(ARGTYPES...args) {
    TYPE result(args...);
    result.init_normalization_constant();
    return result;
  }
};

int main() {
  L1Vector vec = Factory<L1Vector>::construct_and_init<std::initializer_list<double> >({0.25, 0.5, 1.0});
  std::cout << "L1 ";
  for (int k=0; k<vec.size(); ++k)
    std::cout << vec.get_value(k) << " ";
  std::cout << std::endl;

  return 0;
}


オプション3: 実際のキャッシュを使用する:get_normalization_constant新しいタイプ、CacheFunctorとして定義されます。初めてCacheFunctor呼び出されると、戻り値が保存されます。


__init__Pythonでは、基本クラスであっても仮想テーブルが常に存在するため、これは元のコードどおりに機能します。C ++では、これははるかに注意が必要です。

私は本当に助けていただければ幸いです。これは私にとって多くのことです。私はC++で優れたオブジェクト指向設計のコツをつかんでいるように感じますが、非常に効率的なコードを作成する場合は必ずしもそうとは限りません(特に、この種の単純なキャッシュの場合)。

4

2 に答える 2

5

非仮想インターフェースパターンをお勧めします。このパターンは、共通の機能と固有の機能の両方を提供するメソッドが必要な場合に優れています。(この場合、共通のキャッシング、一意性の計算。)

http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface

// UNTESTED
struct NormalizedVector : public std::vector<double> {
 ...
  double normalization_constant;
  bool cached;
  virtual double do_get_normalization_constant() = 0;
  double get_normalization_constant() {
    if(!cached) {
      cached = true;
      normalization_constant = do_get_normalization_constant();
    }
    return normalization_constant;
};

PSあなたは本当に公にから派生するべきではありませんstd::vector

cachedPPキャッシュの無効化は、 falseに設定するのと同じくらい簡単です。


完全なソリューション

#include <vector>
#include <iostream>
#include <iterator>
#include <cmath>
#include <algorithm>

struct NormalizedVector : private std::vector<double> {
private:
  typedef std::vector<double> Base;
protected:
  using Base::operator[];
  using Base::begin;
  using Base::end;
public:
  using Base::size;

  NormalizedVector(std::initializer_list<double> init_list):
    std::vector<double>(init_list) { }
  double get_value(int i) const {
    return (*this)[i] / get_normalization_constant();
  }

  virtual double do_get_normalization_constant() const = 0;
  mutable bool normalization_constant_valid;
  mutable double normalization_constant;
  double get_normalization_constant() const {
    if(!normalization_constant_valid) {
      normalization_constant = do_get_normalization_constant();
      normalization_constant_valid = true;
    }
    return normalization_constant;
  }

  void push_back(const double& value) {
    normalization_constant_valid = false;
    Base::push_back(value);
  }

  virtual ~NormalizedVector() {}
};

struct L1Vector : public NormalizedVector {
  L1Vector(std::initializer_list<double> init_list):
    NormalizedVector(init_list) { get_normalization_constant(); }
  double do_get_normalization_constant() const {
    return std::accumulate(begin(), end(), 0.0);
  }
};

struct L2Vector : public NormalizedVector {
  L2Vector(std::initializer_list<double> init_list):
    NormalizedVector(init_list) { get_normalization_constant(); }
  double do_get_normalization_constant() const {
    return std::sqrt(
      std::accumulate(begin(), end(), 0.0,
        [](double a, double b) { return a + b * b; } ) );
  }
};

std::ostream&
operator<<(std::ostream& os, NormalizedVector& vec) {
  for (int k=0; k<vec.size(); ++k)
    os << vec.get_value(k) << " ";
  return os;
}

int main() {
  L1Vector vec{0.25, 0.5, 1.0};
  std::cout << "L1 " << vec << "\n";

  vec.push_back(2.0);
  std::cout << "L1 " << vec << "\n";

  L2Vector vec2{0.25, 0.5, 1.0};
  std::cout << "L2 " << vec2 << "\n";

  vec2.push_back(2.0);
  std::cout << "L2 " << vec2 << "\n";
  return 0;
}
于 2012-10-02T17:33:03.697 に答える
0

手っ取り早い解決策は、静的メンバー変数を使用することです。

  double get_normalization_constant() const {
    static double tot = 0.0;
    if( tot == 0.0 )
    for (int k=0; k<size(); ++k)
      tot += (*this)[k];
    return tot;
  }

この場合、それは一度だけ計算されます..そして毎回それは最新の値を返します。

注: このダブルトットは、同じタイプのすべてのオブジェクトで共有されます。タイプL1Vectorのオブジェクトを多数作成する場合は、使用しないでください。

于 2012-10-02T17:32:53.557 に答える