连接三个不同的对象

Ema*_*man 16 c++ shared-ptr c++11 c++14

几个小时前我问了一个关于连接矢量的两个元素的类似问题.现在,我想让我的问题更加笼统.我们假设我们有两个double类型的对象double d1, d2.我们希望第三个object(double d3)获取值d1+d2,这样如果我们改变d1或者d2,则d3自动获得新值d1+d2.我们怎样才能用C++做到这一点?

这就是我的意思:

int main(){
double d1,d2,d3;
d1=4;
d2=7;

//some operations to make d3=d1+d2

std::cout<<d3<<endl;// I want it to print 11
d2=-4;
std::cout<<d3<<endl;//Now without any further operations between these line, it should print 0
return 0;     
}
Run Code Online (Sandbox Code Playgroud)

谢谢.

Jar*_*d42 11

您可以创建一个包装器,作为lambda:

double d1 = 0, d2 = 0;
auto l = [&](){ return d1 + d2; };

l(); // 0
d1 = 40;
d2 = 2;
l(); // 42
Run Code Online (Sandbox Code Playgroud)

如果您希望所有变量具有相同的类型,您可以添加类型擦除包装器std::function:

std::function<double()> f1 = [] { return 0; };
std::function<double()> f2 = [] { return 0; };
std::function<double()> sum = [&] { return f1() + f2(); };

std::cout << sum() << std::endl; // 0
f1 = [] { return 40; };
f2 = [] { return 2; };

std::cout << sum() << std::endl; // 42
Run Code Online (Sandbox Code Playgroud)


Tre*_*key 9

您的问题是参数绑定的经典动机.

#include <iostream>
#include <functional>

//generic add
template <typename T>
void Add(T x, T y, T & z){
  z = x + y;
}

int main(){

  //z will change automatically in function call
  double z = 0;

  //bind z as the result
  using namespace std::placeholders;
  auto add = std::bind(Add<double>, _1, _2, std::ref(z));

  //z is implicity passed and changed
  add(6,4);

  //outputs 10
  std::cout << z << '\n';
}
Run Code Online (Sandbox Code Playgroud)

bindreference wrappers可以帮助您实现所需的功能.


Zer*_*ges 7

编写一个包装器,它将存储指向doubles的指针(如原始问题中所推荐的那样).请注意,如果doubles超出范围但counter不会超出范围,这将无效.此外,您可以重载转换为T运算符以摆脱total()功能.

template<typename T>
class counter
{
public:
    void regist(T& d)
    {
        refs.push_back(&d);
    }

    T total()
    {
        T total{};
        for (auto d : refs)
            total += *d;
        return total;
    }

private:
    std::vector<T*> refs;
};

int main(int argc, char* argv[])
{
    double d1 = 1.6;
    double d2 = 7.2;
    double d3 = 1e-4;
    counter<double> cnt;
    cnt.regist(d1);
    cnt.regist(d2);
    cnt.regist(d3);
    std::cout << cnt.total() << std::endl; // 8.8001
    d2 = -7.1;
    std::cout << cnt.total() << std::endl; // -5.4999
}
Run Code Online (Sandbox Code Playgroud)


Whi*_*TiM 5

在编译时,不,充其量,你最终会得到一些令人讨厌的模板和宏黑客仍将受到严重限制.如果您的想法是在编译时,请不要阅读其余的答案

从用户级别(在运行时)?是的你可以.虽然,逻辑非常简单,但您只是想找到一种方法来实际创建和维护表达式树的不变量.要实现它需要一些工作.

所以,让我们来解决逻辑......为简单起见,让我们在这里定义一些基本术语以符合我们的意图

  • An operator是一个最多需要2的函数,operands当被调用时,它产生的结果是另一个operand
  • operand在您的情况下,An 是一个感兴趣的对象,更确切地说是数字double.它可以由您或您生产expression
  • An expression是一个最多占用2 operandsoperatora 的对象,并operand通过调用operator函数产生结果.

我做了一些图纸来说明这个......

表达树

如您所见,箭头显示了知识的方向.

  • Operand知道所涉及的所有表达方式.
  • 一个Expression知道Operands生产出来.

那么,让我们给他们一些身份......

表达树

让我们说,你创建的Operand 1,Operand 2,Operand 4.您开始按以下顺序构建此表达式树:

  1. 所创建的关系(一个Expression之间)Operand 1并且Operand 2其由下式表示Expression1.

  2. Expression1Operator它来构造它来产生它的结果,Operand 3

  3. 您将结果Operand 3与您创建的结果组合Operand 4成一个新表达式Expression2以产生另一个结果,Operand 5


现在,让我们看看当我们决定修改时会发生什么Operand 1.

修改一个操作数的效果

如您所见,修改后的操作数将递归遍历并更新其结果取决于它的所有子表达式(无论是直接还是通过代理).


现在,我们已经有了这个非常简单的想法,我们该怎么做呢.有许多方法可以实现它,它越通用和灵活,性能就越差(在内存和速度方面)

我在下面做了一个简单的实现(显然,远非任何最佳).

template<typename T>
class Operand;

template<typename T>
class Expression {
    std::shared_ptr<Operand<T>> m_operand1;
    std::shared_ptr<Operand<T>> m_operand2;
    std::shared_ptr<Operand<T>> m_result;
    T (*m_operator)(const T&, const T&);

    friend class Operand<T>;

    public:
    Expression(
               T(*operator_func)(const T&, const T&),
               std::shared_ptr<Operand<T>> operand_1,
               std::shared_ptr<Operand<T>> operand_2) :
                   m_operand1(operand_1),
                   m_operand2(operand_2),
                   m_result(std::make_shared<Operand<T>>(T{})),
                   m_operator(operator_func)
    {
    }

    void update(){
        m_result->value() = m_operator(m_operand1->value(), m_operand2->value());
        m_result->update();
    }

    std::shared_ptr<Operand<T>>& result() { return m_result; }

};

template<typename T>
class Operand {
    T val;
    std::vector<std::shared_ptr<Expression<T>>> expressions;
    friend class Expression<T>;

    public:

    Operand(T value) : val(value) {}

    T& value() { return val; }

    void update(){
        for(auto& x : expressions)
            x->update();
    }

    static std::shared_ptr<Operand<T>> make(const T& t){
        return std::make_shared<Operand<T>>(t);
    }

    static std::shared_ptr<Operand<T>>
        relate(
               T(*operator_func)(const T&, const T&),
               std::shared_ptr<Operand<T>> operand_1,
               std::shared_ptr<Operand<T>> operand_2
               ){
        auto e = std::make_shared<Expression<T>>(operator_func, operand_1, operand_2);
        operand_1->expressions.push_back( e );
        operand_2->expressions.push_back( e );
        e->update();
        return e->result();
    }
};

//template<typename T>
//double add(const double& lhs, const double& rhs){ return lhs + rhs; }

template<typename T>
T add(const T& lhs, const T& rhs){ return lhs + rhs; }

template<typename T>
T mul(const T& lhs, const T& rhs){ return lhs * rhs; }

int main()
{
    using DOperand  = Operand<double>;

    auto d1 = DOperand::make(54.64);
    auto d2 = DOperand::make(55.36);

    auto d3 = DOperand::relate(add<double>, d1, d2);
    auto d4 = DOperand::relate(mul<double>, d3, d2);



    //---------------PRINT------------------------//
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value()
              << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = "
              << d4->value() << std::endl;


    //---------------UPDATE ONE VARIABLE------------------------//
    std::cout << "\n\n====================\n" << std::endl;
    std::cout << "changed d1 from " << d1->value() << " to ";
    d1->value() = -863.2436356;
    d1->update();
    std::cout << d1->value() << "\n\n=======================\n\n";



    //---------------PRINT------------------------//
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value()
              << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = "
              << d4->value() << std::endl;


    // *******************************************
    std::cout << "\n\n\n\n\nSizeof(Operand<int>) = " << sizeof(Operand<int>)
              << "\nSizeof(Expression<int>) = " << sizeof(Expression<int>) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

d1 = 54.64
d2 = 55.36
d3 = d1 + d2 = 110
d4 = d3 * d2 = 6089.6

====================
changed d1 from 54.64 to -863.244
=======================

d1 = -863.244
d2 = 55.36
d3 = d1 + d2 = -807.884
d4 = d3 * d2 = -44724.4
Run Code Online (Sandbox Code Playgroud)

在科利鲁看到它

对于简单integral类型,我的使用shared_ptr是一种矫枉过正,我实际上可以用普通指针做到这一点.但是这种实现倾向于概括于类型typename T.

其他要考虑的事情......

  • API选择
  • 内存使用情况
  • 避免周期检测(无限递归更新)
  • ...等等

欢迎评论,批评和建议.:-)