2016-04-30 277 views
16

几个小时前,我问了一个类似的问题,关于连接矢量的两个元素。现在,我想让我的问题更一般化。假设我们有两个类型的对象,即两个对象,即double d1, d2。我们希望第三个对象(double d3)获得值d1+d2,这样如果我们更改d1d2,则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;  
} 

感谢。

+1

好问题! ...这种问题可以在参数化建模中找到......在通用性和性能之间通常会有牺牲。 Jarod42的回答很好。但是对于自动更改,它需要一些工作 – WhiZTiM

+0

当您说“自动获取”时,是否希望在前两个对象之一发生更改时通知第三个对象,或者当您检测到更改时使用拉模式你需要他们吗? –

+0

谢谢大家的回答。 @ChrisDrew我的意思是,如果我改变任何前两个对象,分配给第三个对象的值也会改变,而不需要进一步的显式操作。 – Eman

回答

11

您可以创建一个包装,一个拉姆达:

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

l(); // 0 
d1 = 40; 
d2 = 2; 
l(); // 42 

如果你想,所有的变量具有相同的类型,你可以添加一个类型擦除包装为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 
+1

你应该看看OP的[原始问题](http://stackoverflow.com/questions/36952849/how-to -connect-两元件对的一矢量)。拉姆达不适用于这种情况。 –

+1

@ Jarod42谢谢你的回答。唯一的问题是我想让第三个对象与前两个对象的类型相同,但是在这个代码中,“l”的类型与double的不同。 – Eman

+0

@Eman比发表更多关于你如何期待使用它的代码。你不能有双倍,这本身就是“没有特别的原因” – Zereges

7

编写一个包装器,它将存储指向double的指针(如原始问题中推荐的那样)。当心,这不会工作,如果double会超出范围,但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 
} 
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'; 
} 

bindreference wrappers可以帮助实现你所追求的功能。

4

如果不将d1d2的类型更改为可观察的内容,则无法做到这一点。你可以做这样的事情。

#include <iostream> 
#include <boost/signals2.hpp> 

class ObservableDouble 
{ 
public:  
    boost::signals2::signal<void()> sigChanged; 

    void operator=(double val) 
    { 
     d = val; 
     sigChanged(); 
    } 

    operator double() const { return d; } 

private:  
    double d;  
}; 

int main() 
{ 
    ObservableDouble d1; 
    ObservableDouble d2;   
    double d3; 

    auto add = [&](){ d3 = d1 + d2; }; 

    d1.sigChanged.connect(add); 
    d2.sigChanged.connect(add); 

    d1 = 4.0; 
    d2 = 7.0;   
    std::cout << d3 << std::endl; // prints 11 
    d2 = -4.0; 
    std::cout << d3 << std::endl; // prints 0 
} 

Live on Coliru

+0

谢谢。这是一个非常好的主意。 – Eman

+0

+1对于信号 - 当观察到的结果数量远大于2时,这种方法效率更高。 –

5

在编译的时候,没有,充其量,你会用一些讨厌的模板和宏黑客将仍然受到严重的限制而告终。如果您的想法是编译时,请不要阅读其余的答案

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

所以,让我们,应对逻辑... 为了简单起见,我们这里定义一些基本术语,以适应我们的意图

  • operator是需要最多2 operands这样调用时的函数,它产生的结果是另一个operand
  • 一个operand是一个感兴趣的对象,在你的情况下,数字,更准确地说double。它可以由您产生或expression
  • 一种expression是一个对象,需要至多2 operandsoperator,并通过调用operator功能产生的所得operand

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

Expression Tree

正如你所看到的,箭头显示了知识的方向。

  • Operand知道所有的表达及其参与。
  • 一个Expression知道它产生的Operands

所以,让我们给他们一些身份......

Expression Tree

让我们说,你创建Operand 1Operand 2Operand 4。你开始在这为了建立这样的表达式树:

  1. Operand 1并且由Expression1表示Operand 2之间建立的一种关系(一个Expression)。

  2. Expression1使用它与构建生产其结果OperatorOperand 3

  3. 你结合所产生的Operand 3你创建Operand 4到一个新的表达Expression2产生另一种结果,Operand 5


现在,让我们看看当我们决定修改012时会发生什么。

Effect of modifying one Operand

正如你所看到的,修改后的操作将递归遍历并更新其结果取决于它(不论直接或通过代理服务器)中的所有子表达式。


现在,我们已经有了这个非常简单的想法,我们该如何去做。 有很多方法可以实现它,它的通用性和灵活性越低,它的性能就越差(就内存和速度而言)

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

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; 
} 

输出为:

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 

看到它Live on Coliru

对于简单integral类型,我的shared_ptr使用是矫枉过正,我可以真正做到这与正常的指针。但是这种实现倾向于概括typename T的类型。

想其他的事情......

  • API选择
  • 内存使用
  • 避免循环检测(无限递归更新)
  • ...等

评论欢迎批评和建议。 :-)

+0

非常感谢。这真的很有帮助。在运行时更改值足以满足我想要的操作。 – Eman