这样的for循环被认为是不好的实践吗?

8

以这个例子为例,假设我有一个名为original的std::vector,我想将它一分为二成为两个不同的向量。假设original具有偶数元素。

std::vector<int> firstHalf;
std::vector<int> secondHalf;

for (int i = 0, j = original.size()/2; i < original.size() / 2; i++, j++)
{
    firstHalf.push_back(original[i]);
    secondHalf.push_back(original[j]);
}

更为明显的方法是使用两个单独的for循环,一个用于填充firstHalf,另一个用于填充secondHalf。
像我这样编写for循环是否被认为是不良做法?从我的测试来看,这种解决方案比使用两个单独的for循环略微更有效率。

3
这是一个观点问题,但我认为这不是一种不好的做法。 - R Sahu
1
我认为在循环之前使用 firstHalf.reserve(original.size() / 2 + 1) 可以使其更加高效。 - Jiahao Cai
1
那么 for (std::size_t i = 0, half = v.size() / 2; i != half; ++i) { f.push_back(v[i]); s.push_back(v[i + half]); } 怎么样? - Kerrek SB
2
最好的缓存方式可能是使用两个循环。 - synchronizer
2
original.size() 可能会被多次调用,因此将其值存储在变量中可能更有效率。 - Nipun Talukdar
显示剩余6条评论
5个回答

10

事实上,您可以将您的代码缩减为两行:

std::vector<int> firstHalf(original.begin(), original.begin() + original.size() / 2);
std::vector<int> secondHalf(original.begin() + original.size() / 2, original.end());

原因:

push_back 在元素数量增加时可能重新分配内存。 STL会在一开始分配足够的内存。


你能谈一下这个解决方案相对于原始解决方案的性能吗? - Sumner Evans
4
push_back 可能在元素数量增加时重新分配内存。而 STL 会在开始时一次性分配足够的内存。 - Thomas

6
我认为这不是一个坏的做法,但也不是很好的做法。正如Jett的回答所指出的那样,可以将其简化为:
std::vector<int> firstHalf(original.begin(), original.begin() + original.size() / 2);
std::vector<int> secondHalf(original.begin() + original.size() / 2, original.end());

我觉得最好避免重新计算 original.size()/2

std::size_t halfsize = original.size()/2;
std::vector<int> firstHalf(original.begin(), original.begin() + halfsize);
std::vector<int> secondHalf(original.begin() + halfsize, original.end());

或者,甚至,
std::vector<int>::const_iterator halfway = original.begin() + original.size()/2;

std::vector<int> firstHalf(original.begin(), halfway);
std::vector<int> secondHalf(halfway, original.end());

(在 C++11 及以后版本中,halfsizehalfway 的声明可以使用 auto 来确定类型)。
无论这样做是否更好(例如可读性),都高度主观。
核心信息是,在结果更干净、效果明显等情况下,使用标准算法是个好主意。添加额外的变量来避免重复表达式可以提高可读性。
如果出于某种原因确实需要使用循环(例如你要做的不仅仅是将向量的部分复制到其他向量中),那么请考虑以下几点:
  • 在多次调用 push_back() 之前使用 reserve()
  • 使用向量迭代器而不是数组下标
  • 在循环之前预先计算重复使用的值(例如,std::size_t halfsize = original.size()/2 而不是在循环内部反复计算 original.size()/2)。特别是如果 original 不是 const 的话,因为 - 根据循环所做的事情 - 编译器很难确定它的大小是否会改变。
  • 在循环内使用标准算法,而不是实现嵌套循环。

优化器肯定可以轻松识别出 size 不会改变并避免多次调用它,这是毋庸置疑的。当然,可以争论手动缓存大小可以保证这一点(针对假设中的有限优化器),同时也提高了可读性,但我不认为在实践中需要这样做。 - underscore_d
就像我说的,这取决于循环所做的事情。由于优化器是复杂的代码,有很多方式可以故意或无意中愚弄它们。有很多现实世界中的“有限”优化器,而像将计算从循环中提升出来这样的简单技术很少会有害,无论优化器是否有限。还有可读性的考虑——如何衡量它,赋予它多少价值等等。 - Peter

2
我会将此分成两个循环。这样即使元素数量不是偶数,循环也非常简单。请保留HTML标签。
std::vector<int> firstHalf;
std::vector<int> secondHalf;

size_t middle = original.size()/2;

for (size_t i = 0; i < middle; i++)
{
    firstHalf.push_back(original[i]);
}

for (size_t i = middle; i < original.size(); i++)
{
    secondHalf.push_back(original[i]);
}

但我不会直接称呼您的原始代码为不良做法。

1

为了提高缓存友好性和空间局部性,我建议使用两个循环。在原始代码中,您来回跳转于原始数组的不同部分,这些部分相距数组大小的一半。 最好以步幅1的模式访问数组元素。 此外,值得为子数组保留空间,并保存其他变量,如大小和计数。

size_t size = original.size();
size_t mid_size = size / 2;

std::vector<int> firstHalf(mid_size);
std::vector<int> secondHalf((size - mid_size == mid_size) ? mid_size : mid_size + 1);

size_t i = 0;
for (; i < mid_size; i++) {
    firstHalf[i] = original[i];
}
for (; i < size; i++) {
    secondHalf[i - mid_size] = original[i];
}

Jett的答案非常好。


0

对于像这样简单的代码,很容易理解正在发生什么。但是对于更高级的代码(1000多行),我相信大多数人宁愿将其分成两个for循环。

你所说的“更有效率”是什么意思?你看过汇编代码吗?


他可能对大输入进行了一些计时以确定效率。 - Sumner Evans

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接