在编译的时候,没有,充其量,你会用一些讨厌的模板和宏黑客将仍然受到严重的限制而告终。如果您的想法是编译时,请不要阅读其余的答案
从用户级别(运行时)?是的你可以。虽然逻辑非常简单,但您只需找到一种实用的方法来创建和维护表达式树的不变量。它需要一些工作才能实现。
所以,让我们,应对逻辑... 为了简单起见,我们这里定义一些基本术语,以适应我们的意图
- 的
operator
是需要最多2 operands
这样调用时的函数,它产生的结果是另一个operand
- 一个
operand
是一个感兴趣的对象,在你的情况下,数字,更准确地说double
。它可以由您产生或expression
- 一种
expression
是一个对象,需要至多2 operands
和operator
,并通过调用operator
功能产生的所得operand
。
我做了一些图纸,说明这个....
正如你所看到的,箭头显示了知识的方向。
- 的
Operand
知道所有的表达及其参与。
- 一个
Expression
知道它产生的Operands
。
所以,让我们给他们一些身份......
让我们说,你创建Operand 1
,Operand 2
,Operand 4
。你开始在这为了建立这样的表达式树:
您Operand 1
并且由Expression1
表示Operand 2
之间建立的一种关系(一个Expression
)。
Expression1
使用它与构建生产其结果Operator
,Operand 3
你结合所产生的Operand 3
你创建Operand 4
到一个新的表达Expression2
产生另一种结果,Operand 5
现在,让我们看看当我们决定修改012时会发生什么。
正如你所看到的,修改后的操作将递归遍历并更新其结果取决于它(不论直接或通过代理服务器)中的所有子表达式。
现在,我们已经有了这个非常简单的想法,我们该如何去做。 有很多方法可以实现它,它的通用性和灵活性越低,它的性能就越差(就内存和速度而言)
我在下面做了一个简单的实现(显然,任何最佳)。
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选择
- 内存使用
- 避免循环检测(无限递归更新)
- ...等
评论欢迎批评和建议。 :-)
好问题! ...这种问题可以在参数化建模中找到......在通用性和性能之间通常会有牺牲。 Jarod42的回答很好。但是对于自动更改,它需要一些工作 – WhiZTiM
当您说“自动获取”时,是否希望在前两个对象之一发生更改时通知第三个对象,或者当您检测到更改时使用拉模式你需要他们吗? –
谢谢大家的回答。 @ChrisDrew我的意思是,如果我改变任何前两个对象,分配给第三个对象的值也会改变,而不需要进一步的显式操作。 – Eman