Я подумываю об использовании функций pure/const более сильно в моем коде на С++. (атрибут pure/const в GCC)
Однако мне любопытно, насколько я должен быть строгим, и что может сломаться.
Наиболее очевидным случаем являются отладочные выходы (в любой форме, может быть на cout, в каком-то файле или в каком-то специальном классе отладки). У меня, вероятно, будет много функций, которые не будут иметь никаких побочных эффектов, несмотря на такой отладочный вывод. Независимо от того, сделан ли вывод отладки или нет, это абсолютно не повлияет на остальную часть моего приложения.
Или другой случай, о котором я думаю, - это использование некоторого класса SmartPointer, который может делать некоторые дополнительные вещи в глобальной памяти при работе в режиме отладки. Если я использую такой объект в функции pure/const, он имеет некоторые незначительные побочные эффекты (в том смысле, что какая-то память, вероятно, будет отличаться), которая не должна иметь никаких реальных побочных эффектов (в том смысле, что поведение любой другой).
Похожие также для мьютексов и других вещей. Я могу думать о многих сложных случаях, когда у него есть некоторые побочные эффекты (в смысле того, что какая-то память будет другой, возможно, даже некоторые потоки будут созданы, некоторые манипуляции с файловой системой сделаны и т.д.), Но не имеет вычислительной разницы (все эти побочные эффекты вполне можно было бы оставить без внимания, и я бы даже предпочел это).
Итак, чтобы суммировать, я хочу отметить функции как чистые /const, которые не являются чистыми /const в строгом смысле. Простой пример:
int foo(int) __attribute__((const));
int bar(int x) {
int sum = 0;
for(int i = 0; i < 100; ++i)
sum += foo(x);
return sum;
}
int foo_callcounter = 0;
int main() {
cout << "bar 42 = " << bar(42) << endl;
cout << "foo callcounter = " << foo_callcounter << endl;
}
int foo(int x) {
cout << "DEBUG: foo(" << x << ")" << endl;
foo_callcounter++;
return x; // or whatever
}
Заметим, что функция foo не const в строгом смысле. Хотя, не имеет значения, что foo_callcounter в конце. Также не имеет значения, если инструкция отладки не выполнена (в случае, если функция не вызывается).
Я ожидал бы выход:
DEBUG: foo(42)
bar 42 = 4200
foo callcounter = 1
И без оптимизации:
DEBUG: foo(42) (100 times)
bar 42 = 4200
foo callcounter = 100
Оба случая полностью прекрасны, потому что для моей usecase имеет значение обратное значение bar (42).
Как это работает на практике? Если я отмечаю такие функции, как pure/const, может ли он сломать что-либо (учитывая, что код верен)?
Обратите внимание, что я знаю, что некоторые компиляторы могут вообще не поддерживать этот атрибут. (BTW., Я собираю их здесь.) Я также знаю, как использовать атрибуты этих атрибутов таким образом, чтобы код оставался переносным (через #defines). Кроме того, все компиляторы, которые мне интересны, каким-то образом поддерживают его; поэтому мне все равно, если мой код работает медленнее с компиляторами, которые этого не делают.
Я также знаю, что оптимизированный код, вероятно, будет выглядеть по-разному в зависимости от компилятора и даже версии компилятора.
Очень актуально также эта статья LWN "Последствия чистых и постоянных функций" , особенно глава "Коды". (Спасибо ArtemGr за подсказку.)