1

我想要关于缓存由两个派生类共享的计算的方法的建议。作为说明,我有两种类型的归一化向量 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()重复调用,即使它在构造后没有更改(假设修饰符 likepush_back已适当禁用)。

如果我只考虑一种形式的标准化,我会简单地使用一个双精度值来缓存这个结果:

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被调用,它保存返回值。


在 Python 中,这与最初的编码一样工作,因为虚拟表始终存在,即使在__init__基类中也是如此。在 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.

PPs 使缓存失效就像设置cached为 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 回答