Подтвердить что ты не робот

Ссылки на С++ - это просто синтаксический сахар?

Является ли ссылка на С++ просто синтаксическим сахаром или предлагает какие-либо ускорения в определенных случаях?

Например, вызов по указателю в любом случае включает в себя копию, и это, похоже, верно и для вызова по ссылке. Основополагающий механизм, похоже, одинаков.

Изменить: после шести ответов и много комментариев. Я до сих пор считаю, что ссылки - это просто синтаксический сахар. Если бы люди могли ответить прямо или нет, а если кто-то мог принять принятый ответ?

4b9b3361

Ответ 1

Ссылки имеют более сильные гарантии, чем указатели, поэтому компилятор может оптимизировать более агрессивно. Недавно я видел, что GCC встроил несколько вложенных вызовов через ссылки на функции отлично, но не один через указатели функций (потому что он не мог доказать, что указатель всегда указывал на одну и ту же функцию).

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

Ответ 2

Предположим, что ссылка указана как:

  • Не может быть NULL
  • После инициализации нельзя переопределить другой объект
  • Любая попытка его использования неявно разыгрывает его:

    int a = 5;
    int &ra = a;
    int *pa = &a;
    
    ra = 6;
    
    (*pa) = 6;
    

здесь, как он выглядит при разборке:

    int a = 5;
00ED534E  mov         dword ptr [a],5  
    int &ra = a;
00ED5355  lea         eax,[a]  
00ED5358  mov         dword ptr [ra],eax  
    int *pa = &a;
00ED535B  lea         eax,[a]  
00ED535E  mov         dword ptr [pa],eax  

    ra = 6;
00ED5361  mov         eax,dword ptr [ra]  
00ED5364  mov         dword ptr [eax],6  

    (*pa) = 6;
00ED536A  mov         eax,dword ptr [pa]  
00ED536D  mov         dword ptr [eax],6  

присвоение ссылки является тем же самым с точки зрения компилятора, что и назначение указателя разыменования. Нет никакой разницы между ними, как вы можете видеть (мы не говорим о оптимизации компилятора прямо сейчас) Однако, как упоминалось выше, ссылки не могут быть нулевыми и иметь более сильные гарантии того, что они содержат.

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

Ответ 3

Компилятор не может считать, что указатель не равен нулю; при оптимизации кода он должен либо доказать, что указатель не является нулевым, либо испускает программу, которая учитывает вероятность того, что она равна нулю (в контексте, где это было бы четко определено).

Аналогично, компилятор не может предположить, что указатель никогда не меняет значение. (и он не может предположить, что указатель указывает на действительный объект, хотя мне трудно представить себе случай, когда это имеет значение в четко определенном контексте)

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

Ответ 4

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

Вы не можете взять адрес ссылки, но только то, что упоминается. Вы не можете изменить ссылку после ее создания.

A T& и a T*const (обратите внимание, что const применяется к указателю, а не к указанному) относительно относительно. Принимая адрес фактического значения const и изменяя его, это поведение undefined, так как оно модифицирует (любое хранилище, которое оно использует непосредственно), ссылку.

Теперь на практике вы можете получить хранилище справки:

struct foo {
  int& x;
};

sizeof(foo) почти наверняка будет равен sizeof(int*). Но компилятор может пренебречь возможностью того, что кто-то, непосредственно обращающийся к байтам foo, может фактически изменить упомянутое значение. Это позволяет компилятору сразу прочитать ссылку "указатель", а затем никогда не читать ее снова. Если бы мы имели struct foo{ int* x; }, то компилятор должен был бы доказать каждый раз, когда он сделал *f.x, что значение указателя не изменилось.

Если у вас был struct foo{ int*const x; }, он снова начинает вести ссылку, как в своей неизменности (изменение того, что было объявлено const - UB).


Трюк, который я не знаю о том, как использовать компиляторы, заключается в том, чтобы сжать привязку привязки в лямбда.

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

Исключение - это ссылки, снятые ссылкой, которые в отчете о дефекте для С++ остаются в силе, даже если ссылочная переменная выходит за пределы области. Таким образом, они должны быть захвачены псевдо-указателем.

Для конкретного примера (если есть игрушка):

void part( std::vector<int>& v, int left, int right ) {
  std::function<bool(int)> op = [&](int y){return y<left && y>right;};
  std::partition( begin(v), end(v), op );
}

lambda above может захватывать только указатель кадра стека и знать, где left и right относятся к нему, уменьшая его размер, вместо того, чтобы записывать две ссылки int на (в основном указатель).

Здесь мы имеем ссылки, подразумеваемые [&], существование которых устраняется легче, чем если бы они, где указатели были захвачены по значению:

void part( std::vector<int>& v, int left, int right ) {
  int* pleft=&left;
  int* pright=&right;
  std::function<bool(int)> op = [=](int y){return y<*pleft && y>*pright;};
  std::partition( begin(v), end(v), op );
}

Есть несколько других различий между ссылками и указателями.

Ссылка может продлить время жизни временного.

Это сильно используется в циклах for(:). Оба определения цикла for(:) полагаются на расширение жизненного цикла ссылки, чтобы избежать ненужных копий, а пользователи циклов for(:) могут использовать auto&&, чтобы автоматически выводить самый легкий способ переноса завершенных объектов.

struct big { int data[1<<10]; };

std::array<big, 100> arr;

arr get_arr();

for (auto&& b : get_arr()) {
}

здесь эталонное продление жизни тщательно предотвращает появление лишних копий. Если мы изменим make_arr, чтобы вернуть a arr const&, он продолжает работать без каких-либо копий. Если мы изменим get_arr, чтобы вернуть контейнер, который возвращает big значение элементов (например, диапазон ввода итератора), снова не выполняются лишние копии.

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


Аналогично, ссылки пересылки позволяют обрабатывать данные как const, non-const, lvalue или rvalue разумно. Временные метки отмечены как временные, данные, которые пользователи больше не нуждаются, отмечены как временные, данные, которые будут сохраняться, отмечены как ссылочные значения lvalue.

В ссылках на преимущества есть ссылки на не ссылки, так что вы можете сформировать ссылку rvalue для временного, и вы не можете сформировать указатель на это временное место, не передавая его через ссылочное преобразование ссылочной ссылки на lvalue.

Ответ 5

Нет


Ссылки - это не просто синтаксическая разница; они также имеют разную семантику:

  • Ссылка всегда псевдонизирует существующий объект, в отличие от указателя, который может быть nullptr (контрольное значение).
  • Ссылка не может быть переустановлена, она всегда указывает на один и тот же объект на протяжении всего срока службы.
  • Ссылка может продлить время жизни объекта, см. привязку к auto const& или auto&&.

Таким образом, на уровне языка ссылка является самостоятельной. Остальные детали реализации.

Ответ 6

Раньше были преимущества эффективности, потому что ссылки легче оптимизировать для компилятора. Тем не менее, современные компиляторы стали настолько хороши в этом, что больше нет преимуществ.

В одном огромном значении преимущества есть над указателями, так как ссылка может ссылаться на значение в регистре, в то время как указатели могут указывать только на блоки памяти. Возьмите адрес чего-то, что было бы в регистре, и вы заставили бы компилятор поставить это значение в обычное место памяти. Это может создать огромные преимущества в жестких циклах.

Однако современные компиляторы настолько хороши, что теперь они распознают указатель, который мог бы быть ссылкой для всех целей и целей, и относиться к нему точно так же, как если бы это была ссылка. Это может привести к довольно интригующим результатам в отладчике, где вы можете иметь такой оператор, как int* p = &x, попросить отладчика напечатать значение p, только чтобы он произнес что-то по строкам "p не может быть напечатан", потому что x фактически находился в регистре, а компилятор обрабатывал *p как ссылку на x! В этом случае буквально нет значения для p

(Тем не менее, если вы попытались выполнить арифметику указателя на p, вы заставили бы компилятор больше не оптимизировать указатель, чтобы действовать как ссылка, и все будет замедляться)

Ответ 7

8.3.2 Ссылки [dcl.ref]

Ссылка может рассматриваться как имя объекта

который отличается от указателей, который является переменной (в отличие от ссылки), которая содержит адрес ячейки памяти объекта **. Тип этой переменной - это указатель на Object.

Внутренняя ссылка может быть реализована как указатель, но стандарт никогда не гарантируется.

Итак, чтобы ответить на ваш вопрос: С++ Reference не являются синтаксическим сахаром для указателей. И независимо от того, предоставляет ли он какое-либо ускорение, уже был дан ответ в глубину.

****** Объект здесь означает любой экземпляр, имеющий адрес памяти. Даже указатели - это объекты, а также функции (и, следовательно, у нас есть вложенные указатели и указатели функций). В подобном смысле у нас нет указателей на ссылку, поскольку они не создаются.