LINUX.ORG.RU

Ссылки и типы в C++

 , ,


0

4

Заинтересовал что-то меня вопрос о том, зачем было делать ссылки частью системы типов. Фактически они работают как синонимы, не могут храниться в массивах и пр. и пр. Я уже не говорю о привнесенной в язык сложности, которую усиливает новый стандарт посредством rvalue-ссылок.

При этом появились ссылки исключительно ради перегрузки операторов, т.к. указатели не позволяли это сделать. Если верить D&E.

Не лучше ли было сделать механизм квалификаторов для «синонимов»(что-то вроде ref в C#, только с учетом константности), чтобы программист мог указывать, что функция принимает аргумент/возвращает значение по ссылке, а не по значению. Проблему перегрузки операторов это решает полностью, сложности никакой не представляет, систему типов не усложняет.


C++ появился в: 1983
C# появился в: 2000

bhfq ★★★★★ ()
Ответ на: комментарий от bhfq

Мне интересно, есть ли какая-то причина для такого решения, или же это некоторая ошибка при проектировании языка. Из D&E это не очень ясно.

forCe ()

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

Появились может быть. Но используются и без этого.

AIv ★★★★★ ()
Ответ на: комментарий от AIv

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

forCe ()
Ответ на: комментарий от forCe

Я ХЗ что есть квалификатор. Но если это, о чем я думаю, то ссылки ровно то же самое и делают - рассматривайте их как квалификаторы;-)

Или переменная являющаяся синонимом может храниться в массиве?

AIv ★★★★★ ()
Ответ на: комментарий от AIv

Но если это, о чем я думаю, то ссылки ровно то же самое и делают

Они еще являются частью типа(а чтоб достать тип нужно использовать std::remove_reference<T>::type), могут доставлять определенные трудности при метапрограммировании и пр.

forCe ()
Ответ на: комментарий от AIv

Фактически я имею в виду что-то вроде такого

void foo(ref some_type x);
void foo(ref const some_type x);
ref some_type foo();
ref const some_type foo();

// ...

{
   some_type x;
   ref some_type y = x;
   ref const some_type z = x;

   for (ref auto e : some_container) {
       // ...
   }
}
И этим мы ограничиваемся. При этом тип остается some_type, квалификаторы учитываются лишь при вызове функций/возврате значений.

На самом деле я ничего такого не придумывал, просто мне интересно, зачем делать «ссылочность» частью типа.

forCe ()

ты можешь перезагрузить оператор[], так, что-бы он вернул ссылку. Тогда можно

Vector v;
v[0] = 17;
v["str"] = "zzz";

PS: что там в сишарпе — я не в курсе.

emulek ()
Ответ на: комментарий от forCe

Но при этом ref тут регламентирует способ возврата, а не тип.

forCe ()
Ответ на: комментарий от forCe

Но при этом ref тут регламентирует способ возврата, а не тип.

в C/C++ есть только один способ возврата — возврат какого-то числа(не считая «возврата» void). Потому только ссылка подходит для изменения значения того, что вернулось(указатель не подходит потому, что его надо где-то сохранить, а потом разименовать).

emulek ()
Ответ на: комментарий от emulek

В том, что тип возвращаемого значения тут value_type, а не value_type&.

forCe ()
Ответ на: комментарий от emulek

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

forCe ()

ref плохо вписывается в C++. Логично было сделать их именно похожими на указатели и практически даром при этом гарантировать их применимость вместо указателей в большинстве случаев. Я подозреваю, что ref не позволило бы использовать ссылки в typedef, для преобразования типов и для перегрузки по lvalue/rvalue.

На ссылки стоит смотреть как на другой способ записи указателей с ограниченными возможностями, а не на то чем ref является в C#. Последний значительно более ограничен, а для C++ характерны более универсальные решения (в том же D&E в начале написано про ортоганальность средств языка, которая обеспечивает расширяемость и гибкость).

xaizek ★★★★★ ()
Ответ на: комментарий от forCe

Потому-что спп - статически типизированный язык? Я предположил просто.

nanoolinux ★★★★ ()
Ответ на: комментарий от forCe

В том, что тип возвращаемого значения тут value_type, а не value_type&.

и в чём разница между таким «значением» как у тебя, и ссылкой C++?

emulek ()
Ответ на: комментарий от forCe

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

если честно, я вообще не врубаюсь, что такое «новый тип возврата»?

emulek ()
Ответ на: комментарий от xaizek

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

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

Я подозреваю, что ref не позволило бы использовать ссылки в typedef

А зачем нужен такой typedef?

для преобразования типов

В этом смысле ref может вести себя так же, как и &

для перегрузки по lvalue/rvalue

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

forCe ()
Ответ на: комментарий от emulek

В том, что ссылка в спп - фактически новый тип. А ТС предлагает (предлагал бы) ввести ключевое слово, говорящее компилеру о том, что значение надо возвращать по „ссылке“ (не в смысле ссылке как уже в спп, а в классическом понимании этого слова), а не по значению.

nanoolinux ★★★★ ()
Последнее исправление: nanoolinux (всего исправлений: 1)
Ответ на: комментарий от emulek

Я написал какой - по ссылке. Тоже касается и передачи аргументов.

forCe ()
Ответ на: комментарий от nanoolinux

А ТС предлагает (предлагал бы)

На самом деле я скорее интересуюсь, почему было принято то решение, которое было принято.

forCe ()

Я уже не говорю о привнесенной в язык сложности, которую усиливает новый стандарт посредством rvalue-ссылок.

А о гарантиях оптимизации, которые rvalue references дают поговорить не хочешь? Если в С++98 можно было только надеяться, что компилятор соптимизирует копирование возвращаемого значения, то в С++11 есть способ это гарантировать. Также хотелось бы увидеть альтернативы, скажем, std::unique_ptr на С++98 (с возвратом таких указателей из функции).

Не лучше ли было сделать механизм квалификаторов для «синонимов»(что-то вроде ref в C#, только с учетом константности), чтобы программист мог указывать, что функция принимает аргумент/возвращает значение по ссылке, а не по значению

А как насчёт ссылочных членов классов? На какие-либо общие ресурсы. Использование ссылки вместо голого указателя однозначно указывает на то, что объект не владеет ресурсом и ресурс обязателен для данного объекта.

Begemoth ★★★★★ ()
Ответ на: комментарий от Begemoth
int f(const int &i);

...

f(*(int *)0x0);

ничего не поделаешь, это спп.

nanoolinux ★★★★ ()
Ответ на: комментарий от nanoolinux

Да, но типы подсказывают где должны быть проверки на нулевой указатель.

Begemoth ★★★★★ ()
Ответ на: комментарий от forCe

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

Синтаксически похожи, просто имеют ряд дополнительных ограничений.

А зачем нужен такой typedef?

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

В этом смысле ref может вести себя так же, как и &

Да, но использовать ref, который к типу не относится, в преобразовании типа это как-то странно и не последовательно.

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

Я говорил не о rvalue-ссылках, а о ссылках на rvalue. Это разные вещи. Пример. Хотя, может с ref здесь тоже самое было бы.

xaizek ★★★★★ ()
Ответ на: комментарий от Begemoth

А о гарантиях оптимизации, которые rvalue references дают поговорить не хочешь?

Я уже писал выше. Это не относится к вопросу, который я задал. Но и move-семантика и forwarding можно решить другими средствами.

А как насчёт ссылочных членов классов?

Зачем? Вот честно говоря на практике это приводило лишь к висячим ссылкам. Умные указатели в том или ином виде предпочтительнее. Но, опять же, вопрос не в этом.

forCe ()
Ответ на: комментарий от xaizek

Синтаксически похожи, просто имеют ряд дополнительных ограничений.

Эти ограничения приводят к тому, что ссылки фактически не являются полноценными типами. Но при этом являются типами по правилам языка.

Да, но использовать ref, который к типу не относится, в преобразовании типа это как-то странно и не последовательно.

Эм. Я, видимо, не так понял. Можно кодом?

Хотя, может с ref здесь тоже самое было бы.

Именно.

И да, вроде ссылок кроме C++ больше негде нет. А вот передача аргументов по ссылке - классическая практика.

forCe ()
Ответ на: комментарий от forCe

Эм. Я, видимо, не так понял. Можно кодом?

ref Derived foo = dynamic_cast<ref Derived>(bar);

Выглядит странно. И если ссылки не являются частью системы типов, то отношение наследования как-то странно (неявно) выражаются через эти ref'ы.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

Видимо, будет просто

ref Derived foo = dynamic_cast<Derived>(bar);
Да, странно. Но когда появились ссылки, dynamic_cast'а не было... Ок, с кастом хороший пример, спасибо.

forCe ()

ref T foo(...);

ref T t1 = foo(...); t1 = t2;

/thread

anonymous ()
Ответ на: комментарий от forCe

Все кресты - это памятник ошибкам проектирования языка.

stevejobs ★★★☆☆ ()
Ответ на: комментарий от nanoolinux

В том, что ссылка в спп - фактически новый тип. А ТС предлагает (предлагал бы) ввести ключевое слово, говорящее компилеру о том, что значение надо возвращать по „ссылке“ (не в смысле ссылке как уже в спп, а в классическом понимании этого слова), а не по значению.

т.е. школьникам непонятно, что такое «ссылка» в C++ ? Что такое «ссылка» в сишарпе, школьники уже поняли, да, и теперь хотят, что-бы в C++ сделали тоже самое? Что-бы лишнего не учить?

Ну извините... C++ был придуман задолго до C#, и совсем не для школьников.

Почему-бы школьникам не юзать сишарп?

emulek ()
Ответ на: комментарий от forCe

Я написал какой - по ссылке. Тоже касается и передачи аргументов.

ты тоже школьник, или всё-же объяснить в состоянии?

emulek ()
Ответ на: ref T foo(...); от anonymous

Тут поведение будет аналогично &

forCe ()
Ответ на: комментарий от emulek

Я просто спросил, почему было принято такое решение. В D&E обоснованы или просто объяснены многие принятые решения. Но вот про этот момент ничего не сказано.

forCe ()
Ответ на: комментарий от emulek

Ну как-то так:

void foo(int x); // передача по значению
void foo(ref const int x); // передача по ссылке
void foo(ref int x); // передача по ссылке на изменяемый объект

Для возвращаемого значения:

int foo(); // возврат по значению
ref const int foo(); // возврат по ссылке
ref int foo(); // возврат по ссылке на изменяемый объект

Для локальных ссылок:

    int x; // значение
    ref const int y = x; // ссылка
    ref int z = x; // ссылка на изменяемый объект

При этом ref тут просто соглашение о способе передачи/возврата, а не часть типа, хотя работает примерно так же, как и ссылки в C++.

Пока вижу только одну проблему - это, как тут выше сказали, касты.

forCe ()
Ответ на: комментарий от forCe

Я просто спросил, почему было принято такое решение.

а я считаю, что другого и быть не могло. Функция всегда возвращала какое-то число, вот она и сейчас возвращает число. Ссылка — это уже разименованный указатель в сишечке.

А вводить какую-то «особую форму» конечно никто не стал. Зачем плодить лишние сущности?

emulek ()
Ответ на: комментарий от forCe

а... понял. Понял, чего ты не понял.

Открою Страшную Тайну: ссылки в C/C++ не нужны.

Есть указатели. Просто в примере выше с массивом пришлось-бы писать:

Vector v;
*(v[17]) = 123;

такое УГ было-бы несколько мягко скажем неудобно. Потому и ввели сахар, который называется «ссылки».

emulek ()
Ответ на: комментарий от emulek

Чтобы не плодить лишние сущности в виде очень странного типа...

forCe ()
Ответ на: комментарий от emulek

Я это понимаю. Я не понимаю, зачем этот сахар делать частью типа

forCe ()
Ответ на: комментарий от forCe

Чтобы не плодить лишние сущности в виде очень странного типа...

чем сущность ref лучше?

Учитывая, что сущность «указатель» УЖЕ была, и отказываться от неё Страуструп не хотел. Потому и сделал сущность «ссылка», которая по жизни — тот же указатель, только в профиль.

emulek ()
Ответ на: комментарий от forCe

Я это понимаю. Я не понимаю, зачем этот сахар делать частью типа

потому-что иначе пришлось-бы делать сахар для сущности «функция». Ну вроде как потом в C++11 сделали лямбды. Но лямбд вообще в сишечки отродясь не было, вот их и пришлось делать с нуля.

emulek ()
Ответ на: комментарий от emulek

Я не говорю, что с указателями что-то не так. Как раз наоборот. Это самостоятельный тип, как и другие. А ссылки являются очень особенным типом с кучей ограничений и правил.

forCe ()
Ответ на: комментарий от emulek

потому-что иначе пришлось-бы делать сахар для сущности «функция».

Зачем?

forCe ()
Ответ на: комментарий от forCe

Я не говорю, что с указателями что-то не так. Как раз наоборот. Это самостоятельный тип, как и другие. А ссылки являются очень особенным типом с кучей ограничений и правил.

давай так: какие ограничения/правила тебе непонятны?

потому-что иначе пришлось-бы делать сахар для сущности «функция».

Зачем?

потому-что функция в C/C++ возвращает одно значение какого-то типа. Т.е. что-бы функция стала lvalue, она должна вернуть либо указатель, который надо разименовать, либо указатель, который уже разименован(aka ссылку). Больше просто нечего. Никаких «объектов» в сишечке нет, есть только числа. Всё остальное — сахар.

Например нет массивов, есть сахар для указателей — []. Нет ссылок, есть другой сахар над указателями. Нет селекторов методов класса, есть третий сахар, это перезагружаемый operator->(). Нету даже виртуальных функций, есть четвёртый сахар над указателем на таблицу указателей на функции, в сишечке это тоже работает, только выглядит жутко.

emulek ()
Ответ на: комментарий от emulek

давай так: какие ограничения/правила тебе непонятны?

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

либо указатель, который уже разименован(aka ссылку).

Ну так и пусть возвращает. Только пусть это не будет частью типа.

И хватит мне объяснять то, что не относится к вопросу.

forCe ()
Ответ на: комментарий от forCe

достаточно ввести дополнительные соглашения для передачи аргументов и возвращаемого значения.

можно ещё раз, попроще, как это должно выглядеть? А то я не понял, чем

ref int foo(); // возврат по ссылке на изменяемый объект
отличается от
int &foo();

И да, с аргументами вопросов нет, ибо аргумент это всегда rvalue. Его никто в принципе менять не может. Его можно только читать.

А вот с возвращаемым значением — мне непонятно.

emulek ()
Ответ на: комментарий от emulek

Отличается тем, что int& - это отдельный полутип «ссылка на инт». В то время как ref - это просто квалификатор, который говорит нам о том, что мы имеем дело с синонимом, а не со значением. Но тип у него int, а не какой-то другой. Это вполне решает изначальную проблему Страуструпа с перегрузкой операторов, не усложняя при этом систему типов.

forCe ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.