问题第一
C++ 中是否有一种优雅的解决方案来防止必须声明复杂的对象变量,这些变量只在循环外的循环中使用,以提高效率?
详细解释
一位同事提出了一个有趣的观点。到我们的代码策略,它声明(解释):始终使用最小范围的变量并在第一次初始化时声明变量。
编码指南示例:
// [A] DO THIS
void f() {
...
for (int i=0; i!=n; ++i) {
const double x = calculate_x(i);
set_squares(i, x*x);
}
...
}
// [B] DON'T do this:
void f() {
int i;
int n;
double x;
...
for (i=0; i!=n; ++i) {
x = calculate_x(i);
set_squares(i, x*x);
}
...
}
这一切都很好,这当然没有错,直到你从原始类型转移到对象。(对于某种界面)
例子:
// [C]
void fs() {
...
for (int i=0; i!=n; ++i) {
string s;
get_text(i, s); // void get_text(int, string&);
to_lower(s);
set_lower_text(i, s);
}
...
}
在这里,字符串 s 将被破坏,每个循环周期都会释放内存,然后每个循环get_text
函数都必须为 s 缓冲区重新分配内存。
写起来显然更有效:
// [D]
string s;
for (int i=0; i!=n; ++i) {
get_text(i, s); // void get_text(int, string&);
to_lower(s);
set_lower_text(i, s);
}
因为现在 s 缓冲区中分配的内存将在循环运行之间保留,我们很可能会节省分配。
免责声明: 请注意:由于这是循环并且我们正在谈论内存分配,因此我不认为一般考虑这个问题是过早的优化。当然,有些情况和循环的开销并不重要。但是n
有一个比开发人员最初期望的更大的唠叨倾向,并且代码有在性能确实很重要的环境中运行的唠叨倾向。
无论如何,现在“通用”循环构造的更有效方法是违反代码局部性并声明复杂对象不合适,“以防万一”。这让我相当不安。
请注意,我考虑这样写:
// [E]
void fs() {
...
{
string s;
for (int i=0; i!=n; ++i) {
get_text(i, s); // void get_text(int, string&);
to_lower(s);
set_lower_text(i, s);
}
}
...
}
没有解决方案,因为可读性会受到更大的影响!
进一步思考,get_text
函数的接口无论如何都是非惯用的,因为输出参数无论如何都是昨天的,一个“好”的接口会按值返回:
// [F]
for (int i=0; i!=n; ++i) {
string s = get_text(i); // string get_text(int);
to_lower(s);
set_lower_text(i, s);
}
在这里,我们不会为内存分配支付双倍的费用,因为极有可能s
会通过 RVO 从返回值构造,所以对于 [F],我们支付与[C] 相同的分配开销。然而,与[C] 的情况不同,我们无法优化此接口变体。
因此,底线似乎是使用最小范围(可能)会损害性能并使用干净的接口我至少认为按值返回比 out-ref-param 的东西会阻止优化机会要干净得多——至少在一般情况下.
问题不在于有时为了效率而不得不放弃干净的代码,问题在于一旦开发人员开始发现这种特殊情况,整个编码指南(参见 [A]、[B])就会失去权威.
现在的问题是:见第一段