我正在研究DSEL并希望拥有以下内容:
Bra ij;
Ket kl, cd;
(ij||kl); // initialize some array
(ij||cd); // ditto
....
T(i,j,k,l)*(ij||kl); // do some math without recomputing (ij||kl)
所以基本上我想让表达式充当变量。有可能吗?
到目前为止,我的想法是拥有一个“单例”工厂,使用表达式(ij|kl)
生成/查找数组。还有什么吗?
答案 0 :(得分:2)
如果您不想重新计算ij||kl
,那么只需将它们存储到表达式返回的任何类型的变量中。这正是变量存在的原因之一。
好的,这是我能想到的唯一方法,虽然听起来不是很漂亮。你可以做的是,当运营商||函数被调用,将操作数以及结果存储到实例变量中(如果运算符||是某个类的成员函数),或者存储到静态分配的变量中(如果在其上声明了运算符||,则为寂寞)。 / p>
下一次运营商||调用,检查操作数是否与上次调用操作数相同。如果是,只需返回您存储的最后一个结果。否则,计算新结果。
这应该可以解决问题。令人讨厌的部分是它需要将操作数复制到其他变量,这可能是昂贵的,具体取决于具体情况。但是,如果变量是不可变的,你可以保留指向操作数的指针,这会更便宜一些。
如果您想更进一步,可以使用map
或其他东西来存储多个先前调用的操作数和结果。这样,如果您需要为多个不同的计算执行此操作,这将起作用。
答案 1 :(得分:2)
要做这种事情,很明显你需要某种全球知识。换句话说,您将必须构建某种全局或半全局结构,以保留操作/表达式的记录。
我建议你构建一个图形(可能带有BGL),其中节点是表达式(变量是零arity运算符的特例)。如果将每个操作数作为事件顶点连接到操作符的顶点,则可以构造图形。稍后,当需要实际评估表达式时,您可以首先使用一些修剪规则遍历图形,以便消除冗余操作。如果你确保图中没有任何顶点重复,那么修剪是隐含的,我猜。
如果你想避免使用全局数据,我建议你使用某种表达式管理器类并将所有操作注册到它。例如,像这样:
int main() {
expression_handler expression; //have some class to handle a sequence of operations.
expression
<< (ij||kl)
<< (ij||cd)
<< ....
<< T(i,j,k,l)*(ij||kl); //register all the expressions, in order.
expression.evaluate(); //evaluate the expression (possibly optimizing the graph before)
return 0;
};
答案 2 :(得分:2)
我会将Ken和Mikael的建议结合起来。这种东西很容易被操作符重载,特别是当你在每一侧都有自定义类型时。
Bra :: operator ||方法返回一个临时对象ExpressionHandle。
当它创建ExpressionHandle时,它还会注册ExpressionBody 内部有一个全局存储(这可以让你避免使用实例 变量但意味着您必须保留所有表达式,直到结束 该计划或明确的发布。)
ExpressionBody对象既是声明(表达式树)又是可选结果。您可以使用惰性技术仅在最终调用它们时对其进行评估。
当像
这样的行创建ExpressionHandle时,会发生调用ExpressionBodyT(I,J,K,L)*(IJ || KL);
上面的(ij || kl)将创建一个ExpressionBody并寻求注册它但找到一个现有的全局实例,所以只需返回一个指向全局实例的ExpressionHandle。
operator *(const ExpressionHandle&amp;,blah)会要求ExpressionBody返回其结果,此时它会返回一个缓存的结果或执行第一次评估并缓存它。
答案 3 :(得分:0)
编译器的工作是决定表达式是否需要重新评估,还是可以从之前的评估中重复使用。让编译器完成它的工作。
您应该尽可能简洁明了地表达自己(无论您是否使用变量完全取决于您和上下文)。但是,如果表达式使用未更改的对象(即,未使用非成本方法分配的对象),则编译器可能会优化并重新计算相同的表达式。
请注意。为了帮助编译器确保正确地将方法标记为const。