如何设计这个程序,并考虑到简单的修改?

Sza*_*lcs 8 c++

这个问题是关于如何设计程序,以便进行某些修改.

我有一个类,它包含一些(非平凡的)数据,并有几个成员函数来更改这些数据.

有时我需要计算这些数据的一些属性.但是,每次改变都要从头开始重新计算它.相反,计算这些属性的小更新要快得多.

我有几个这样的属性,我需要能够轻松地添加到我的班级或从我的班级删除(或打开/关闭)进行一些数值实验.该类仅由我自己修改并用于数值模拟(科学代码).

具体例子

假设我有一个包含数字的类x.但我也需要2^x("属性" x).基本课程是:

class C {
    double x;

public:
    C() : x(0.0) 
    { }

    void inc() { x += 1; } 
    void dec() { x -= 1; } 
    void set(double x_) { x = x_; } 
};
Run Code Online (Sandbox Code Playgroud)

但现在我需要跟踪2^x并在每次x更改时不断更新此值.所以我最终得到了

class expC {
    double expx;

public:        
    expC(const double &x) {
        recompute(x);
    }

    void inc() { expx *= 2; } // fast incremental change
    void dec() { expx /= 2; } // fast incremental change
    void recompute(const double &x) {
        expx = std::pow(2, x); // slow recomputation from scratch
    }
};


class C {
    double x;

    expC prop1; // XX

public:
    C() : x(0.0), prop1(x) // XX 
    { }

    void inc() { 
        x += 1;
        prop1.inc(); // XX 
    }
    void dec() { 
        x -= 1; 
        prop1.dec(); // XX
    }
    void set(double x_) { 
        x = x_;
        prop1.recompute(x); // XX
    }
};
Run Code Online (Sandbox Code Playgroud)

XX标志着我需要对课堂做出的改变C.这是很多变化,容易出错.有几个属性变得更加复杂,我甚至相互依赖.

class C {
    double x;

    expC  prop1; // XX
    someC prop2; // XX

public:
    C() : x(0.0), prop1(x), prop2(x, prop1) // XX 
    { }

    void inc() { 
        x += 1;
        prop1.inc(); // XX 
        prop2.inc(); // XX 
    }
    void dec() { 
        x -= 1; 
        prop1.dec(); // XX
        prop2.dec(); // XX
    }
    void set(double x_) { 
        x = x_;
        prop1.recompute(x); // XX
        prop2.recompute(x, prop1); // XX
    }
};
Run Code Online (Sandbox Code Playgroud)

问题:这样的程序有什么好的设计?我确信它可能比上面做的更好.目标是:1)轻松添加/删除此类属性或打开/关闭其计算2)性能至关重要. inc并且dec在紧密的内环中被称为并且相对较少.出于性能原因,它们不能成为虚拟的.

实际上x是一种更复杂的数据结构.可以考虑例如在图表中添加/删除边缘,以及在过程中跟踪其度序列.


更新

@ tobi303要求我展示如何使用这个类.它的方式类似于:

void simulate(C &c) {
    for (/* lots of iterations */) {
        c.inc();
        double p1 = c.prop1.value();
        double p2 = c.prop2.value();
        if (condition(p1,p2))
            c.dec();
    }
}
Run Code Online (Sandbox Code Playgroud)

或者说:

  • 进行(随机)改变
  • 更改后获取属性值
  • 根据新的属性值,决定是接受还是撤消更改.

它实际上是类似于Metropolis-Hasting算法的蒙特卡罗模拟.

一个具体的例子可能是类C(状态)中的"数据" 是Ising模型的自旋状态(对于熟悉它的人),属性是系统的总能量和总磁化强度.单次旋转翻转后更新速度要快于从头开始重新计算.在实践中我没有Ising模型,我有一些更复杂的东西.我有几个属性,一些快速计算,一些慢(实际上我有一些辅助数据结构,有助于计算属性).我需要尝试不同属性的组合,所以我经常更改我在代码中包含的内容.有时我会实现新属性.当我不需要已经实现的属性时,我需要能够出于性能原因关闭其计算(有些计算速度非常慢).

coy*_*508 2

只是偷懒,在需要时不要计算属性。它将删除大量代码和不必要的计算。

当您确实需要您的属性时,如果它尚未在缓存中,请计算它。因此,每个属性都需要一个布尔值来判断缓存是否是最新的,并且每次x更新时都需要使布尔值无效。

基本上:

class C {
    double x;

    template <typename Value> struct cachedProp {
        bool cache = false;
        Value value;
    }

    cachedProp<expC> prop1;
    cachedProp<someC> prop2;
    //...

    void invalidateCache() {
         prop1.cache = false;
         prop2.cache = false;
         //...
    }
public:
    expC getProperty1() {
        if (!prop1.cache) {
            recalculateProp1();
            prop1.cache = true;
        }
        return prop1.value;
    }

    void inc() {
        x += 1;
        invalidateCache();
    }
};
Run Code Online (Sandbox Code Playgroud)

编辑:一个更懒的解决方案是不存储布尔值cache,而是存储与上次更新相对应的整数,并在 中维护一个计数器C。每次缓存失效时,计数器C都会增加。获取时propX,如果计数器不匹配propX.lastUpdate,则更新 `propX.

这样,使缓存失效只是一项操作,而不必更新所有属性的缓存。