假设您有一个类似的类:
class A {
public:
A(const size_t m, const size_t n) :
m_(m), n_(n), data_(m*n ? new double[m*n] : nullptr) {}
~A() { delete[] data_; }
void foo() const {
// m_*n_ evaluated each iteration?
for (size_t i = 0; i<m_*n_; ++i)
data_[i] = 0.0;
// this probably wont but what about the above?
for (auto& i : *this)
i = 0.0;
}
double* begin() const { return data_; }
double* end() const { return data_ + m_*n_; }
private:
size_t m_, n_;
double* data_;
};
由于foo是const和m_,n_是成员变量,编译器应该知道这些不能改变。是每次评估产品还是编译器优化它?
答案 0 :(得分:0)
问题1:
// m_*n_ evaluated each iteration?
for (size_t i = 0; i<m_*n_; ++i)
data_[i] = 0.0;
答案:可能,但你永远不知道。编译器变得非常聪明。
通过将m_
和n_
声明为const
成员,您可以大大提高赔率。无论如何你应该这样做,恕我直言,因为那个阶级的逻辑需要它;如果m_
或n_
发生变化,则需要重新分配data_
。将变量声明为const
告诉编译器即使它看不到类的所有成员函数的所有定义,也可以假定m_
和n_
不要改变。
问题2:
for (auto& i : *this)
i = 0.0;
在这种情况下,根据定义,不的结束值将被重新计算。这就是定义语句范围的方式。
一般来说,之间存在很大差异:
for (auto p = container.begin(), lim = container.end();
p != lim;
++p) { ... }
和
for (auto p = container.begin();
p != container.end();
++p) { ... }
在第一种情况下,假设在循环期间不修改容器,因为修改将使迭代器无效(例如lim
迭代器)。 (有些容器类型不适用。但对于大多数容器类型,在循环期间应避免修改。)
在第二种情况下,每次都会计算限制值,因此如果修改了容器,则没有问题。至少,最终测试没有问题。您仍然需要确保p
不能被无效,对于某些容器类型 - 例如,无序的映射和集合 - 元素序列可以通过修改来更改,因此添加新元素可能会导致一些迭代中缺少的元素。
语法for ( variable : expression)
的范围被明确定义为转换为上述第一种迭代样式,其中限制在迭代开始时计算一次,因此可能使限制迭代器结果无效的突变在未定义的行为。
在大多数情况下,您应该避免此类突变,并且应该使用范围语法或等效样式。但是有些应用程序每次都重新计算限制是合适的。这样一个用例的最好例子可能就是工作清单,可以用std::deque
建模:
for (auto work = workqueue.begin();
work != workqueue.end();
++work) {
/* ... */
if (some_condition) {
work_queue.emplace_back(work_item);
}
/* ... */
}
例如,这是图算法中的常见风格。