В С++ можно объявить ссылку, чтобы указать, что ничто никогда не изменит ее?

Если я делаю

typedef void Cb();

int foo(int const& a, Cb cb) {
  int x = a;
  cb();
  return x - a;
}

и скомпилировать с g++ -O3 -save-temps -c foo.cpp, я вижу, что вычитание сохраняется, а если cb(); закомментировано, вся функция оптимизируется на

xorl    %eax, %eax

Есть ли что-то, что я могу сделать для спецификации параметра a, так что вычитание будет оптимизировано независимо от вызова cb() и без принуждения a к уникальной ссылке (то есть, что это может быть указано в другом месте, но что ни одна из этих ссылок не будет изменена)?

Ответы

Ответ 1

Там расширение __restrict, вы можете попробовать это на gcc.godbolt.org:

typedef void Cb();

int foo(const int & __restrict a, Cb cb) {
  int x = a;
  cb();
  return x - a;
}

Любопытно, что только clang делает оптимизацию, gcc не делает это.


Обратите внимание, что ограничение на сглаживание рассматривается как часть стандарта С++:

Возможно, в будущем вы можете сделать это по стандарту.

Ответ 2

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

static int var;

void func()
{
    var++;
}

// ...
foo(var, func);

Я не знаю какого-либо атрибута, специфичного для компилятора, который вы можете сказать, что cb() не будет изменять a.

Ответ 3

Почему бы вам просто не переместить строку int x = a; под вызов функции?

Если cb() не влияет ни на x, ни на a, вы должны это делать, и я думаю, что компилятор снова оптимизирует вызов, потому что x не может измениться между двумя вызовами. Если есть причина, по которой вы не можете изменить порядок этих двух вызовов, вы, вероятно, не сможете оптимизировать ее в первую очередь.

Это не то, что вы можете намекать на компилятор, потому что нет способа гарантировать, что ни x, ни a не изменились после вызова cb().

Подумайте об этом как о порядке доступа для чтения/записи. Если во время cb() нет доступа для чтения или записи к a и x, вы можете выполнить ручное переупорядочение вызова функции.

Если написано a или x, вы не можете изменить порядок, и оптимизация будет неправильной.

Если читать x, вы не можете переупорядочить вызов функции, но если x действительно только прочитано, вы можете прочитать вместо a и определить x только после вызова, так как он будет иметь такое же значение как a, если бы вы объявили его перед вызовом.

Ответ 4

Вы можете использовать C restrict в ссылке, если ваш компилятор поддерживает это расширение.
(Некоторые компиляторы разрешают __restrict или __restrict__, которые являются частью пространства имен реализаций.)

Это обещание от вас компилятору о том, что объект не сглажен нигде, и он может его оптимизировать.
Если вы солгали компилятору, вы получите поврежденный код, которого вы заслуживаете.

Ответ 5

__ атрибут __ ((const)) достаточно

Как описано в: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Function-Attributes.html, он сообщает компилятору, что данная функция не изменяет глобальные (хотя и может их читать).

Существует также подмножество pure const, что также запрещает глобальные чтения.

Рассмотрим следующий упрощенный пример:

int __attribute__((const)) g();

int f(int a) {
  int x = a;
  g();
  return x - a;
}

В g++ 4.8 x86_64 -O3 он компилируется в:

  • xor %eax,%eax с const
  • большая функция, которая не допускает, что a не изменяется без const

Кажется, что нет более тонкого атрибута, который говорит, что функция не изменяет заданную переменную по мере необходимости.

Может ли __attribute __ ((const)) применяться к указателям на функции?

Попробуйте:

int f(int& a, void __attribute__((const)) (*g)(void)) {
    int x = a;
    (*g)();
    return x - a;
}

Снова компилируется xor %eax,%eax и большая функция без const, поэтому ответ да.

Этот синтаксис задавался по адресу: Функциональный указатель на функцию __attribute __ ((const))?

Он также появился в 2011 году в списке рассылки по адресу: http://comments.gmane.org/gmane.comp.gcc.help/38052 В то время, по крайней мере, он работал только для некоторых атрибутов.

Может ли __attribute __ ((const)) быть добавлен в typedef?

Это работает:

typedef void __attribute__((const)) (*g_t)(void);

int f(int& a, g_t g) {
    int x = a;
    (*g)();
    return x - a;
}

или

typedef void (g_t)(void);

int f(int& a, g_t __attribute__((const)) g) {
    int x = a;
    g();
    return x - a;
}

Но я не мог найти способ как поместить атрибут в typedef, так и передать функцию, а не указатель, например:

typedef void __attribute__((const)) (g_t)(void);

GCC дает предупреждение о том, что в этом случае атрибут игнорировался.