2015-05-14 2 views
-1

Предположим, у вас есть класс, как так:для границ цикла пересчитывается каждая итерация?

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 является сопзЬ и m_, n_ переменные-члены, компилятор должен знать, что они не могут изменить. Является ли продукт оценен каждый раз или компилятор оптимизирует его?

+1

Это зависит от компилятора, от его конкретных настроек оптимизации, от положения реляций Марса и Луны и т. Д. ** Но действительно ли это имеет значение? ** Вы обнаружили, сравнивая, что это значительное узкое место в вашем коде ? Если нет, просто не беспокойтесь. –

+0

Вы всегда можете посмотреть на сборку и посмотреть, что она делает. – NathanOliver

+0

Собственно, компилятор должен видеть, что 'm_' и' n_' не меняются во время цикла и вытаскивают это вычисление из цикла. –

ответ

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) явно определен для перевода в первый из вышеперечисленных стилей итерации, где предел вычисляется один раз в начале итерации, и, следовательно, мутации, которые могут привести к аннулированию лимита-итератора, приводят к неопределенному поведению ,

В большинстве случаев вам следует избегать таких мутаций, и вы должны использовать синтаксис диапазона или эквивалентный стиль. Но есть приложения, в которых перерасчет предела каждый раз является подходящим. Вероятно, лучшим примером такого сценария использования является Worklist, который может быть смоделирован с std::deque:

for (auto work = workqueue.begin(); 
    work != workqueue.end(); 
    ++work) { 
    /* ... */ 
    if (some_condition) { 
    work_queue.emplace_back(work_item); 
    } 
    /* ... */ 
} 

Это общий стиль в алгоритмах на графах, например.

Смежные вопросы