什么是双重求值,为什么应该避免双重求值
What is double evaluation and why should it be avoided?
我正在阅读c++中使用的宏,如
#define max(a,b) (a > b ? a : b)
可以导致"双求值"。谁能给我举个例子,什么时候会发生双重求值,为什么它不好?
注::令人惊讶的是,我找不到任何详细的解释时,谷歌除了一个例子在Clojure(我不能理解)。
假设你写了这个:
#define Max(a,b) (a < b ? b : a)
int x(){ turnLeft(); return 0; }
int y(){ turnRight(); return 1; }
然后像这样调用它:
auto var = Max(x(), y());
你知道turnRight()
将被执行两次吗?宏Max
将扩展为:
auto var = (x() < y() ? y() : x());
在计算条件x() < y()
之后,程序在y() : x()
和true
之间进行所需的分支,它在第二次调用y()
。查看Live On Coliru
简单地说,将表达式作为参数传递给类函数的宏,Max
可能会对该表达式求值两次,因为在宏定义中使用宏形参时,该表达式将被重复。记住,宏由预处理器处理。
所以,底线是,不要仅仅因为您希望它是泛型而使用宏来定义函数(在这种情况下实际上是一个表达式),而使用函数模板
可以有效地完成。PS: c++有一个std::max
模板函数
a
和b
在宏定义中出现两次。因此,如果您将它与具有副作用的参数一起使用,则副作用将执行两次。
max(++i, 4);
如果i = 4
在调用之前,将返回6。由于这不是预期的行为,您应该选择内联函数来代替max
这样的宏。
考虑以下表达式:
x = max(Foo(), Bar());
其中Foo
和Bar
如下:
int Foo()
{
// do some complicated code that takes a long time
return result;
}
int Bar()
{
global_var++;
return global_var;
}
然后在原max
表达式中展开如下:
Foo() > Bar() ? Foo() : Bar();
无论哪种情况,Foo或Bar都将执行两次。因此花费的时间比必要的要长,或者改变程序状态的次数比预期的要多。在我的简单Bar
示例中,它不会始终返回相同的值。
C和c++中的宏语言在"预处理"阶段由专用解析器处理;这些令牌被翻译,然后输出被适当地输入到解析器的输入流中。#define
和#include
标记是C或c++解析器本身不能识别的。
这很重要,因为它意味着当一个宏被"展开"时,它指的是字面上的。鉴于
#define MAX(A, B) (A > B ? A : B)
int i = 1, j = 2;
MAX(i, j);
c++解析器看到的是
(i > j ? i : j);
但是,如果我们将宏用于更复杂的内容,则会发生相同的展开:
MAX(i++, ++j);
展开为
(i++ > ++j ? i++ : ++j);
如果我们传递了一个函数调用:
MAX(f(), g());
将扩展为
(f() > g() ? f() : g());
如果编译器/优化器可以证明f()
没有副作用,那么它将把它视为
auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;
如果不能,则必须调用f()和g()两次,例如:
#include <iostream>
int f() { std::cout << "f()n"; return 1; }
int g() { std::cout << "g()n"; return 2; }
#define MAX(A, B) (A > B ? A : B)
int main() {
MAX(f(), g());
}
实时演示:http://ideone.com/3JBAmF
类似地,如果我们调用一个extern
函数,优化器可能无法避免调用该函数两次。
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么是 std::exception::what() 以及为什么要使用它?
- 此测试()中发生了什么意外过程?为什么总是覆盖 ch[0 1 2..]?
- 为什么无论你输入什么,这"while(cin.get(str,3))"只运行一次?
- 为什么此指针值不能转换为整数的规则是什么?
- 这是什么代码?为什么它有效?C++
- 为什么或在什么情况下,你会将参数作为C++中的引用(或指针)传递给函数?
- 您好,我实际上想了解以下代码.有人可以详细说明代码它到底在做什么吗?为什么它会在第 31 行崩溃
- 什么是流的标记,为什么流中只有 1 个标记?
- 什么是非营利组织???我的问题是我不明白为什么有人会使用它
- scanf( "%d" , array[i] + 1)是什么意思?为什么+1在那里?
- 什么'!((n % 5 != 0) ||(n % 20 == 0))'变身?为什么呢?我似乎不明白
- 长有什么问题?为什么自动减去 1?
- 为什么我在启动任务时收到成功代码,但它什么也不做?
- 为什么我应该在scanf()-家族成员中包含一个长度修饰符作为参数?有什么好处?使用长度修改器进行扫描的作用
- 为什么左值不能改变自己。左值用户是什么意思?
- 为什么我的代码说"Yes"什么时候应该说"No"?
- 这段代码中的arr[s[i]-'a']有什么用,为什么我们用"a"减去所有字符?
- C++ -- 为什么"什么"在捕获范围内打印"未知异常"?
- 为什么/什么时候我们应该更喜欢使用 std::swap;swap(a, b) over std::iter_swap(&a, &b)?