Автор Тема: C++ инициализация ссылок  (Прочитано 57374 раз)

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #60 : Март 20, 2013, 04:39:15 pm »

Оптимальность определяется в контексте конкретного кода, задачи, ресурсов и ТЗ.
ммм... не то.... ре формулирую вопрос... с чего где прописана ОБЯЗАННОСТЬ компилятора оптимизировать этот момент?
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.

В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях  компилятора С++
2. Хорошо , а как вы интерпретируете  const int & i=45; ?

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #61 : Март 20, 2013, 04:47:04 pm »
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.

В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях  компилятора С++
Я ровно на это и отвечал. Создание алиаса (ссылки) обязано НЕ ПРИВОДИТЬ к созданию нового объекта и соответственно вызову конструкторов. Иначе это будет не алиас, а самостоятельный объект.

2. Хорошо , а как вы интерпретируете  const int & i=45; ?
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #62 : Март 20, 2013, 04:56:45 pm »
Дык, это не оптимизация. Тут имеем тупо алиас на алиас, алиасы, вообще говоря, не должны создавать новых переменных/объектов (в том числе не должны вызывать какие-либо конструкторы). Ну вот оно и не создает.

В случае const Foo bar; имеем не алиас а самостоятельную константу, то есть этот самый bar не должен измениться когда изменится оригинальное значение. Константность у ссылки и у переменной имеет разный смысл.
1. этот вопрос относился к приведенному в КОНЦЕ варианту фунюкции Foo (возвращающей ссылку на сторонний обьект) -где и нашлось различие.. где гарантия что оно будет наблюдаться во ВСЕХ реализациях  компилятора С++
Я ровно на это и отвечал. Создание алиаса (ссылки) обязано НЕ ПРИВОДИТЬ к созданию нового объекта и соответственно вызову конструкторов. Иначе это будет не алиас, а самостоятельный объект.

2. Хорошо , а как вы интерпретируете  const int & i=45; ?
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
1. ХОРОШО - тогда вопрос в чем отличие этого варианта от банальной    O &o (без модификатора const )
2.  а как вы интерпретируете  const int i=45; ?

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #63 : Март 20, 2013, 05:17:12 pm »
Как алиас на число 45. Как именно это будет выглядеть в машкоде - дело десятое. Важна семантика.
1. ХОРОШО - тогда вопрос в чем отличие этого варианта от банальной    O &o (без модификатора const )
O& o -- это алиас разрешающий модификацию, то есть он может быть только на не константу.

2.  а как вы интерпретируете  const int i=45; ?
Как создание константы i равной 45.
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #64 : Март 20, 2013, 05:22:36 pm »
Да, нюанс, вот так можно написать:
volatile const int  i = 45;

А вот так нельзя:
volatile const int&  i = 45;
Ошибка компиляции.

Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #65 : Март 20, 2013, 06:02:46 pm »

А вот так нельзя:
volatile const int&  i = 45;
Ошибка компиляции.

Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
надо же, а у меня компилирует без проблем и предупреждений  ;)

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #66 : Март 20, 2013, 06:44:57 pm »

А вот так нельзя:
volatile const int&  i = 45;
Ошибка компиляции.

Что в общем то логично - ссылка не создает новую переменную или объект, это тупо алиас. А вот определение константы создает новый объект/переменную.
надо же, а у меня компилирует без проблем и предупреждений  ;)
У тебя компилятор явно не реализует стандарт С++. Такими компиляторами славится мелкософт, соответственно предполагаю, что собираешь ты студией (и скорее всего у тебя там не отключены нестандартные расширизмы мелкософтовые и изменения в языке).
Собственно разные вменяемые компиляторы можешь попробовать тут: http://liveworkspace.org/code/31Hll5$2

Ошибка такая:
Compilation finished with errors:
source.cpp:2:25: error: volatile lvalue reference to type 'const volatile int' cannot bind to a temporary of type 'int'
   volatile const int&  i = 45;
                        ^   ~~
1 error generated.
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #67 : Март 20, 2013, 08:29:32 pm »
У тебя компилятор явно не реализует стандарт С++. Такими компиляторами славится мелкософт, соответственно предполагаю, что собираешь ты студией (и скорее всего у тебя там не отключены нестандартные расширизмы мелкософтовые и изменения в языке).
Да , точно.

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #68 : Март 21, 2013, 03:54:55 pm »
Цитировать
Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
Разумеется. Присваивание указателя переменной указательного типа не вызывает лишних копирований.

Цитировать
Это первое семантическое отличие. А вот второе:
Небольшая магия, скрывающая отсутствие необходимости вызывать конструктор переноса, поскольку функции create передаётся адрес памяти, куда и будет помещён объект. Ровно такую же магию можно разрешить и указателям. Если в левой части присваивания указатель,а в правой — значение, то указателю будет присвоен адрес на стеке. Собственно, именно так скрыто и идёт работа со ссылками.

Я подытожу. Здесь и далее синоним — дополнительное имя объекта.

1) Ссылки — это автоматически разыменовываемые указатели. В ряде случаев их использование проще соптимизировать компилятору.
2) Ссылки не обязательно инициализируются один раз. Прыжок назад позволит повторить присваивание.
3) Ссылки не являются физическим синонимом объекта (alias), поскольку их срок жизни может быть больше срока жизни объекта, то есть ссылка может хранить адрес удалённого объекта или объекта на стеке. Ссылки не являются логическим синонимом объекта, так как их можно переустановить.
4) Ссылки могут содержать любые адреса, включая null. Абсолютно легальный код без предупреждений:

Struct* p = NULL;
Struct& ref = *p;

5) Физическая привязка к объекту невозможна без хранения информации о нём. Практически всегда (за вычетом оптимизаций) это адрес объекта. Следовательно, во-первых, ссылка занимает дополнительную память (синоним, как утверждают, памяти дополнительной не забирает), во-вторых, оперирует исключительно адресом, в независимости от его верности: в примере выше разыменование не вызывает исключения, а &ref возвращает 0. В-третьих, поскольку ссылка хранит указатель, то возникают накладные расходы на разыменование, чего не должно быть у синонима.

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

7) Страуструп запретил переустанавливать ссылки по субъективной личной причине неудачного опыта с Algol68:
Цитировать
Цитировать
The reason that C++ does not allow you to rebind references is given in Stroustrup's "Design and Evolution of C++" :

It is not possible to change what a reference refers to after initialization. That is, once a C++ reference is initialized it cannot be made to refer to a different object later; it cannot be re-bound. I had in the past been bitten by Algol68 references where r1=r2 can either assign through r1 to the object referred to or assign a new reference value to r1 (re-binding r1) depending on the type of r2. I wanted to avoid such problems in C++.

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

8) Указатель и ссылкы взаимоконвертируемы:
Цитировать
The C++ standard specifies that a cast reinterpret_cast<U&>(t) is equivalent to *reinterpret_cast<U*>(&t)

9) Личное субъективное наблюдение.  С++ FAQ и адепты языка зачастую повторяют заученные мантры и в этом плане напоминают культистов. Очень странно, учитывая, что большинство из них практикующие профессионалы, понимающие, что стоит за высокопарными фразами, не имеющими к практике никакого отношения.

10) Ничего не имею против автоматически разыменовываемых указателей. Однако возводимая на пустом месте сложность и высокопарность лишь запутывает людей при изучении языка. Все магические свойства ссылок вытекают из того факта, что (*p) не может быть nullptr без вылетов при доступе к объекту. Однако все недостатки и свойства указателей сохраняются: удалённые объекты, нулевые указатели и адреса на стеке могут быть значениями ссылок.


vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #69 : Март 21, 2013, 07:02:56 pm »
1) Ссылки — это автоматически разыменовываемые указатели. В ряде случаев их использование проще соптимизировать компилятору.

Тут valexey уже кучу доводов привел почему ссылки - не указатели. Все мало? :)

2) Ссылки не обязательно инициализируются один раз. Прыжок назад позволит повторить присваивание.

Прыжок назад - это новая ссылка. Отвыкаем думать в терминах VAR секции на всю процедуру ;) Семантически переменная "создается" в месте объявления и разрушается в конце блока. Что там происходит в случае goto - даже думать не хочу (неинтересно). Вот в таком коде:
for(int i = 0; i < 10; i++)
{
     int var = 1;
     int const& ref = i;
}
переменная var и ссылка ref будет создана и разрушена 10 раз. Никаких "повторных присваиваний" тут нет.

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

Struct* p = NULL;
Struct& ref = *p;

Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.

7) Страуструп запретил переустанавливать ссылки по субъективной личной причине неудачного опыта с Algol68:

Вполне валидная причина, нет?

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

Приведи хоть один пример где это нужно и который не может быть переписан без потери наглядности.

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

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

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #70 : Март 21, 2013, 07:21:25 pm »
Ну и еще одно отличие ссылки от указателе - указатель это first-class citizen в С++. С ним можно делать ровно то же самое, что и с переменной любого другого типа. Ну, кроме всего прочего, у него например можно взять адрес.

А вот ссылка - нет. Это не first-class citizen. У ссылки нет собственного адреса, ссылку нельзя копировать. Не бывает указателя на ссылку (а вот указатель на указатель бывает!) и не бывает ссылки на ссылку.

Да, и в рассуждениях выше, у меня сложилось впечатление, что Berserker регулярно путает понятие адреса и указатея. Это две принципиально разные сущности :-D

В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #71 : Март 21, 2013, 07:45:29 pm »
Цитировать
Тут valexey уже кучу доводов привел почему ссылки - не указатели. Все мало?
Не вижу ни одного примера, который бы противоречил моим словам.

Цитировать
Прыжок назад - это новая ссылка. Отвыкаем думать в терминах VAR секции на всю процедуру
Вообще-то я думаю в терминах реального железа, ассемблера и конструкций С++.

Цитировать
Что там происходит в случае goto - даже думать не хочу (неинтересно).
Там осуществляется переход назад командой jmp/jxx, так же, как и в случае цикла. После чего снова выполняется выражение и конкретная ячейка памяти, которую вы зовёте ссылкой, получает конкретное новое значение. Ну никакой магии и всё абсолютно в терминах и по правилам языка.

Цитировать
переменная var и ссылка ref будет создана и разрушена 10 раз. Никаких "повторных присваиваний" тут нет.
Про блоки мне уже пояснили.

Цитировать
Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.
Не может в каком мире? В каких компиляторах не может? Все проверки включены, ни одна низкоуровневая возможность не использована. Код абсолютно корректен с точки зрения языка. Не Вы ли писали, что ссылка гарантирована не NULL, не мёртвая и не висячая? Какой смысл писать то, что не соответствует действительности? А теперь попробуйте сделать то же самое в Обероне. Чтобы VAR-параметр вылетал при обращении к нему или содержал мусор. Вот Оберон может компилировать под разные целевые платформы с разными возможностями организации динамической памяти. А С++ ближе к железу всё-таки.

Цитировать
Вполне валидная причина, нет?
Нет.
1) Страуструп всё-таки оставил один и тот же синтаксис и для присваивания значения переменной и для инициализации ссылки.
2) И первый пункт разрулить и убрать неоднозначность можно было бы при помощи введения ключевого слова или нового оператора (например, ===).

Цитировать
Приведи хоть один пример где это нужно и который не может быть переписан без потери наглядности.
Элементарно. Мне нужно обменять адреса двух ссылок. То есть swap со скоростью указателей. В парсере PrevLexem и ThisLexem постоянно обмениваются. Негоже мне вызывать тормознутый std::swap с реальным копированием. Вот и приходится возвращаться к указателям. И да, лексемы нужно уметь устанавливать (на самом деле устанавливать приходится много полей, которые можно было бы сделать ссылками). При этом синтаксис указателей мне абсолютно не нужен. Он неудобен и излишен (тоже один из вопросов критики С++: два оператора для доступа к элементам структур).

Цитировать
Так что делать с перегружаемыми операторами в отсутствии ссылок
А зачем убирать ссылки? Достаточно дать возможность их менять, обменивать и т.д. Хотя скажу честно. Если бы доступ к полям структур был единым через "." и для переменных-указателей и для обычных, то ссылки стали бы менее нужны. Фактически, у них осталось бы только инициализация при объявлении возможно(!) корректным значением.

Цитировать
В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
Какие компиляторы реализуют ссылки не как указатели и пользовались ли Вы такими хоть раз в жизни? Представляете ли Вы себе реализацию компилятора по стандарту С++, где ссылки на физическом уровне не являются указателями? Указатель — это ячейка памяти (2, 4, 8 байтов), хранящая адрес блока памяти.

Цитировать
Ну и еще одно отличие ссылки от указателе - указатель это first-class citizen в С++. С ним можно делать ровно то же самое, что и с переменной любого другого типа. Ну, кроме всего прочего, у него например можно взять адрес.
Товарищи, смотрите:

1) В язык добавляется ссылка как автоматически разыменовываемый указатель. При присваивании адреса (а за именем переменной стоит её адрес, за возвращаемой из функции ссылкой — адрес и т.д) он ведёт себя как указатель. Во во всех остальных случаях как (*p).
2) Добавляется синтаксический сахар с целью оптимизаций. Такой сахар можно добавить и указателям. Он имеет под собой исключительно физическую подоплёку.
3) Накладывается ряд ограничений на использование ссылок в языке. Эти ограничения облегчают оптимизации, да, но не меняют физического/логического смысла ссылок и в свою очередь сужают возможности для их применения.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #72 : Март 21, 2013, 08:04:37 pm »
Цитировать
В некоторых случаях реализация ссылки действительно делается через использование адресов, но ссылки никогда не используют указатели. Впрочем, это все уже нюансы какой-то конкретной реализации под какую-то конкретную платформу.
Какие компиляторы реализуют ссылки не как указатели и пользовались ли Вы такими хоть раз в жизни? Представляете ли Вы себе реализацию компилятора по стандарту С++, где ссылки на физическом уровне не являются указателями? Указатель — это ячейка памяти (2, 4, 8 байтов), хранящая адрес блока памяти.
Да, конечно пользовался и пользуюсь. Да, все ими пользуются. :-)

Простой пример:
int main() {
    volatile int i;
    volatile int& rep = i;
    volatile int* volatile ptr = &i;
    i    = 1;
    rep  = 2;
    *ptr = 3;
    return 0;
}

Берем clang и генерим код. Смотрим в сгенереный код:
define i32 @main() nounwind {
  %i = alloca i32, align 4                        ; <i32*> [#uses=3]
  %ptr = alloca i32*, align 4                     ; <i32**> [#uses=2]
  volatile store i32* %i, i32** %ptr
  volatile store i32 1, i32* %i
  volatile store i32 2, i32* %i
  %1 = volatile load i32** %ptr                   ; <i32*> [#uses=1]
  volatile store i32 3, i32* %1
  ret i32 0
}
Найди мне тут пожалуйста "указатель" через который реализовалась ссылка ref. Собственно под ref вообще ничего не выделяется ни на стеке, ни где-то там еще. Её в сгенереном коде нет вовсе.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #73 : Март 21, 2013, 08:11:42 pm »
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #74 : Март 21, 2013, 08:20:49 pm »
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.

Ты недооцениваешь современные компиляторы и C++ :-)

int some = 13;
int& two() {return some;}

int main() {
    int& ref = two();
    ref = 42;
    return 0;
}

Сгенерированный код:
@some = global i32 13, align 4                    ; <i32*> [#uses=2]

define i32* @_Z3twov() nounwind readnone {
  ret i32* @some
}

define i32 @main() nounwind {
  store i32 42, i32* @some
  ret i32 0
}

Покажи мне тут, пожалуйста, где у нас ссылка в сгенерированном коде :-)

Вообще, это очень распространенная ошибка - пытаться разобраться в семантике языка пероецируя все в байтики и инструкции процессора (причем в сверически-наивной реализации под какую-то одну конкретную архитектуру). Эдак можно додуматься до того, что в обероне локальные переменные все на стеке всегда живут :-)
Y = λf.(λx.f (x x)) (λx.f (x x))