C ++设计:用于函数结果的全局存储

时间:2014-05-05 20:40:23

标签: c++ c++11 memoization

考虑以下代码(使用gcc 4.7.2编译):

#include<iostream>
#include<memory>

struct Base
{
    ~Base() {}
    virtual double operator()(double x) const = 0;
};

template<typename F, typename G> struct Compose;    

struct A : public Base
{
    virtual double operator()(double x) const {return x;}  //suppose this is a hard-to.calculate function, lots of temporaries, maybe also time-dependent, etc
    template <typename F>
    Compose<A,F> operator()(const F& f) const {return Compose<A,F>(*this,f);}
};

struct B : public Base
{
    virtual double operator()(double x) const {return x*x;}  //suppose this is a hard-to.calculate function, lots of temporaries, maybe also time-dependent, etc
    template <typename F>
    Compose<B,F> operator()(const F& f) const {return Compose<B,F>(*this,f);}
};

struct C : public Base
{
    virtual double operator()(double x) const {return x*x*x;}  //suppose this is a hard-to.calculate function, lots of temporaries, maybe also time-dependent, etc. 
    template <typename F>
    Compose<C,F> operator()(const F& f) const {return Compose<C,F>(*this,f);}
};

template<typename F, typename G>
struct Compose : public Base
{
      Compose(const F &_f, const G &_g) : f(_f), g(_g) {}
      F f;
      G g;
      virtual double operator()(double x) const {return f(g(x));}
};

int main()
{
    double x=2.0;
    A a;
    B b;
    C c;

    std::shared_ptr<Base> ptrBase = std::make_shared<A>(A());
    std::shared_ptr<A> ptrA = std::make_shared<A>(a);

    std::cout<< a(x) <<std::endl;
    std::cout<< ptrBase->operator()(x) <<std::endl;
    std::cout<< ptrA->operator()(x) <<std::endl;

    std::cout<< b(a(x)) <<std::endl;

    std::cout<< c(b(a(x))) <<std::endl;

    std::cout<< a(c(b(a(x)))) <<std::endl;
    std::cout<< ptrA->operator()(c(b(ptrBase->operator()(x)))) <<std::endl;
}

输出:

2
2
2
4
64
64
64

好的,一个简短问题的长期前奏。这里发生了几个冗余计算,例如,A()(x)的结果被计算六次(一些通过对象,一些通过指针)。如何实现存储已评估函数值的全局输出数组?

我想到的一种方法应该是为每个(组合的)类实现唯一的函数std::string id(),加上一个全局std::map<std::string,double>来存储结果,然后检查是否(结果存在。

你在这里看到其他可能更好的方法吗?提前谢谢。



很抱歉,虽然有一些相似之处(“memoization”这个词似乎在某种程度上触发反射),但我真的不明白为什么这应该重复......但我愿意讨论。

在我看来,上述案例比链接线程中的案例复杂得多(例如,它不仅仅是斐波纳契函数)。而且,到目前为止,我可以看到突出显示的memoization类将以不同的方式处理对象和指针(至少不进行进一步编辑)。我的目的是得出一种模式,其中每个结果只计算一次,考虑如何调用它。

到目前为止,我正在使用CRTP的静态结果类,这导致以下代码(使用gcc 4.7.2编译。):

#include<iostream>
#include<memory>
#include<string>
#include<map>


struct Base
{
    virtual ~Base() {}
    virtual double operator()(double x) const = 0;
    virtual std::string id() const = 0;
};

template<typename F, typename G> struct Compose;    

template<typename T>
struct Result
{
    virtual ~Result() {}
    double get(double x) const
    {
    return mem.find(x)->second;
    }

    bool isSet(double x) const {it=mem.find(x); return it!=mem.end();}

    //get the previously found result by isSet(x)
    double get() const
    {
    return it->second;
    }
protected:
    //the set function is const, as it works only on static variables
    //don't know whether it is the best idea, but it allows for a const operator() later...
    void set(double x, double y) const
    {
    mem.insert(std::make_pair(x,y));
    }
private:
    static std::map<double, double> mem;
    static std::map<double, double>::const_iterator it;
};


template<typename T> std::map<double, double> Result<T>::mem;
template<typename T> std::map<double, double>::const_iterator Result<T>::it=Result<T>::mem.begin();


struct A : public Base, public Result<A>
{
    virtual double operator()(double x) const
    {
    if(isSet(x))
    {
        return get();
    }
    else
    {
        double y=x;
        set(x,y);
        std::cout<<"setA   ";
        return y;
    }
    }

    template <typename F>
    Compose<A,F> operator()(const F& f) const {return Compose<A,F>(*this,f);}
    virtual std::string id() const {return "A";}
};

struct B : public Base, public Result<B>
{
    virtual double operator()(double x) const
    {
    if(isSet(x))
    {
        return get();
    }
    else
    {
        double y=x*x;
        set(x,y);
        std::cout<<"setB   ";
        return y;
    }
    }
    template <typename F>
    Compose<B,F> operator()(const F& f) const {return Compose<B,F>(*this,f);}
    virtual std::string id() const {return "B";}
};

struct C : public Base, public Result<C>
{
    virtual double operator()(double x) const
    {
    if(isSet(x))
    {
        return get();
    }
    else
    {
        double y=x*x*x;
        set(x,y);
        std::cout<<"setC   ";
        return y;
    }
    }
    template <typename F>
    Compose<C,F> operator()(const F& f) const {return Compose<C,F>(*this,f);}
    virtual std::string id() const {return "C";}
};


template<typename F, typename G>
struct Compose : public Base, public Result<Compose<F,G> >
{
      Compose(const F &_f, const G &_g) : f(_f), g(_g) {}
      F f;
      G g;
      virtual double operator()(double x) const
      {
      if(this->isSet(x))
      {
          return this->get();
      }
      else
      {
          double y=f(g(x));
          this->set(x,y);
          std::cout<<"set"<<this->id()<<"   ";
          return y;
      }
      }
      virtual std::string id() const {return f.id() + "(" + g.id() + ")";}
};



int main()
{
    double x=2.0;
    A a;
    B b;
    C c;

    std::shared_ptr<Base> ptrBase = std::make_shared<A>(A());
    std::shared_ptr<A> ptrA = std::make_shared<A>(A());

    std::cout<<"-------------------------------"<<std::endl;
    std::cout<< a(x) <<std::endl;
    std::cout<<ptrBase->operator()(x) <<std::endl;
    std::cout<<ptrA->operator()(x) <<std::endl;
    std::cout<<"-------------------------------"<<std::endl;
    std::cout<<c(x)<<std::endl;
    std::cout<<C()(x)<<std::endl;
    std::cout<<C()(x)<<std::endl;
    std::cout<<"-------------------------------"<<std::endl;

    auto ba= b(a);
    std::cout<<ba(x) << std::endl;

    auto cba= c(ba);
    std::cout<<cba(x)<< std::endl;

    auto acba= a(cba);
    std::cout<<acba(x)<<std::endl;
}

输出:

-------------------------------
setA   2
2
2
-------------------------------
setC   8
8
8
-------------------------------
setB   setB(A)   4
setC(B(A))   64
setA(C(B(A)))   64
-------------------------------

好的,有些事情需要注意:

  1. 通过从静态结果类继承,有趣的是所有不同的称为A(通过对象,通过指针到对象,通过指针到基础)检索存储的结果(只有一个)组)。对于C()也是如此,它被调用三次但只设置一次。

  2. 为了预测下一次反射,我知道多重继承可能很糟糕,但这些类似乎很好地分开了。组合可能会获得相同的行为,所以请忘记这一点。

  3. 事实上,这还不是一个真正的记忆类,因为它只是存储首先计算的结果。因此它也会给出错误的结果[例如。对于C(B(A))]。但是,这可以通过地图或其他方式轻松治愈。我最后会这样做,目前只关于模式(EDIT:已完成)。

  4. 作为最后一点,我知道与看似重复的线程不同的解决方案并不意味着不同的问题。然而,它可能( - 希望)在这个页面上产生一些扩展的memoization-topic,所有人都可以从中受益。

    我期待着你的想法。提前谢谢!



    编辑2:帮助自己是一件好事,现在代码正如我所寻找的那样。我在上面的帖子中更新了它。

    的变化:

    1. 添加了一个地图,以实际制作一个真正的memoization类。注意:双重比较是危险的,因此最好通过一个更合适的比较函数而不是标准的相等。

    2. 将结果数组的静态成员设为私有,并且设置者受到保护,因为只有函数类可能会更改结果。

    3. 模板参数不会给出地图类型(如在链接的线程中)。这是我不需要的开销(也可能是YAGNI)。

    4. 现在我不再和自己说话了。评论仍然受欢迎。谢谢,大卫

0 个答案:

没有答案