LINUX.ORG.RU

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

 , ,


0

4

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

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

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



Последнее исправление: CYB3R (всего исправлений: 1)

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

Это вполне решает изначальную проблему Страуструпа

ref int i = f(); &int - на что должен указывать? На i? На то что вернула f?

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

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

А не по указателю? По крайней мере в шарпе как раз по указателю так как null передать можно. На С++ ссылку не очень похоже.

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

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

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

i - это синоним объекта в данном случае. Т.е. поведение будет аналогично текущему int &. Я потому и не понимаю, зачем было делать «ссылки» частью типа, если полноценного типа все-равно не сделать...

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

& является частью типа. ref - нет.

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

Вроде бы нельзя.

Вот так можно:

class Program
{
    static void f1(ref Test t)
    {}
    
    static void f1(Test t)
    {}
    
    
    static void Main(string[] args)
    {
        Test t = null;
        f1(ref t); // так
        f1(t); // и так
    }
}

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

Причём для иммутабельных типов изменения снаружи не будут видны, а для остальных очень даже будут.

В D такая же фигня. Не лучше было бы по умолчанию передавать по константной ссылке/указателю? Тем более, что в D они, в отличии от С# есть.

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

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

На мой взгляд, что «тип, который типом не является», что ref - «дополнительные» сущности. И меняя одно на другое ничего особо не выигрываем. Чем одно сильно проще другого (как при изучении языка новичком, так и при повседневном использовании), (мне) не понятно.

Тем более, что remove_reference не сильно часто использовать приходится.

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

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

т.к. не имеет значения(по крайней мере у него нельзя взять адрес)

ЩИТО?

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

Ну возьми адрес у ссылки. Если для тебя это новость, то ты получишь адрес объекта, на который указывает ссылка, а не адрес ссылки.

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

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

Если для тебя это новость, то ты получишь адрес объекта, на который указывает ссылка, а не адрес ссылки.

Да, кэп. Где проблема?

Зачем делать типом то, что нельзя сделать полноценным типом, особенно при наличии возможности решить проблемы другим способом...

Шла вторая страница, а ты все не мог внятно объяснить, чем твой ref лучше &.

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

Где проблема?

Это не проблема. А особенность. У типа ссылки нет полноценного значения. Под капотом это указатель, но адрес его не получить. Массив ссылок не создать, например. И т.д. Какой смысл в таком типе? Это просто синоним, привязка к уже существующему объекту. Это не самостоятельный тип, как указатель, например.

Шла вторая страница, а ты все не мог внятно объяснить, чем твой ref лучше &.

Тем, что не добавляет в язык полутип с кучей ограничений. Я тут не критикой языка занимаюсь и не предложения вношу. Я спросил о причинах. В D&E это не описано, но мало ли может быть где-то этот вопрос освещался.

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

В то время как ref - это просто квалификатор, который говорит нам о том, что мы имеем дело с синонимом, а не со значением.

т.е. если я правильно понимаю, функция с ref это совсем НЕ функция, а lvalue, т.е. принципиально другая ерунда?

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

угу, решает. Создавая два вида функций:

1. rvalue, старые функции

2. lvalue — новые функции, которые никакие и не функции.

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

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

у ссылки нельзя взять адрес потому, что она сама по себе — адрес. Нельзя брать адрес у адреса. Адрес можно брать только у некой переменной, В КОТОРОЙ лежит адрес.

а так же порождает код со всякими там std::remove_reference и т.д. и т.п., не является усложнением системы типов

ссылка в C++ является синтаксическим сахаром, и не является самостоятельным типом. Потому она и не усложняет систему типов.

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

На мой взгляд, что «тип, который типом не является», что ref - «дополнительные» сущности. И меняя одно на другое ничего особо не выигрываем.

в C++ мы выигрываем совместимость с C. Т.к. int &foo() это всё та же самая функция, возвращающая в аккумуляторе адрес целого. Просто тут сам адрес никуда не записать, запись будет В адрес. Т.е. результат работы функции не меняется — это адрес. Меняется использование этого адреса, в классической сишечке данный адрес где-то сохранялся, а потом использовался как указатель, а в C++ он используется прямо сейчас, без сохранения.

Ну а в сишарпе, как я понимаю, ввели новую ерунду.

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

Вот с С# ввели ref (в смысле при вызове надо тоже указывать), чтобы случайно не модифицировать того чего не надо

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

using System;

namespace test
{
  class Program 
  {
	static void Main (string[] args)
	{
	  var s1 = "str";
	  var s2 = "str";
	  StrFunc1(s1);
	  StrFunc2(ref s2);
	  Console.WriteLine(s1);
	  Console.WriteLine(s2);
	}

	static void StrFunc1(string s)
	{
	  s = "modified";
	}

	static void StrFunc2 (ref string s)
	{
	  s = "modified";
	}
  }
}

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

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

потому-что ты его УЖЕ получил.

Массив ссылок не создать, например.

хватит делить на ноль.

Какой смысл в таком типе?

его можно вернуть, что-бы получить lvalue, без промежуточного сохранения в указателе и разименовывании оного.

Также его можно передать функции operator+() например, потому-что она не умеет сама разименовывать указатели, а попытается их складывать и получит HEX, т.к. сложение указателей это UB. Можно конечно где-то его сохранить и разименовать, но это требует памяти и усложняет код(в общем случае).

но мало ли может быть где-то этот вопрос освещался.

К.О. не пишет книг. Извини.

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

StrFunc1(string s)

тут передаётся вовсе не s, а копия s

static void StrFunc2 (ref string s)

а тут передаётся сама s. Но в сишечке это невозможно, там передаются лишь копии, которые ты скопировал перед вызовом функции. Например в машинный стек командой push. Т.ч. ты можешь

1. передать саму s (если конечно твой стек и архитектура как-то это позволяет. Вот для int обычно позволяет)

2. передать адрес s (копию)

3. передать адрес s, но сказать, что это не адрес, а «сама s»

Но передать саму s ты не можешь потому, что в сишечке это невозможно. А значит, твой новый ЯП будет несовместим напрямую с сишечкой, т.е. для каждой функции в сишечке (glibc, libcrypto, и ещё Over9000 либ) ты будешь писать свою обёртку. И получится у тебя php или C#.

А вот в C++ ты можешь смело пользоваться функциями C напрямую. (обратное не совсем верно, но и не нужно обычно)

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

у ссылки нельзя взять адрес потому, что она сама по себе — адрес. Нельзя брать адрес у адреса. Адрес можно брать только у некой переменной, В КОТОРОЙ лежит адрес.

Я об этом и говорю. Что фактически у ссылки нет значения и нет переменных. Но «тип» есть. Хотя семантически ссылка - это просто еще одно имя к существующему значению.

и не является самостоятельным типом.

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

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

Т.к. int &foo() это всё та же самая функция, возвращающая в аккумуляторе адрес целого.

ref работает точно так же как и int&.

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

потому-что ты его УЖЕ получил.

Я в курсе. Ты когда-нибудь перестанешь капитанствовать? Я могу получить адрес указателя, я могу создавать массив указателей и т.д. и т.п. И указатель является типом. Но с ссылкой нельзя проделать ничего. Поэтому не понятно, что она делает в системе типов.

К.О. не пишет книг. Извини.

Так почему же ты не ответишь на вопрос, если ответ очевиден?

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

Как и в случае int& - это просто адрес. Еще раз, для аргументов, возвращаемых значений и локальных переменных ref бы работал точно так же, как и ссылки. Он просто не был бы частью системы типов. И тип у i был бы int. Да тут даже с точки зрения банальной логики при перегрузке операторов. Ты же определяешь новый оператор для типа my_type, а не типа my_type&. Значит это всего лишь способ передачи, а не новый тип...

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

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

Я разве не ответил? Что-бы не ломать сишную функцию, которая возвращает число какого-то типа.

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

Т.к. int &foo() это всё та же самая функция, возвращающая в аккумуляторе адрес целого.

ref работает точно так же как и int&.

и чем ты не доволен, если «работает также»? Про шашечки я тебе тоже рассказал.

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

Я в курсе. Ты когда-нибудь перестанешь капитанствовать? Я могу получить адрес указателя, я могу создавать массив указателей и т.д. и т.п. И указатель является типом. Но с ссылкой нельзя проделать ничего. Поэтому не понятно, что она делает в системе типов.

-- Почему нельзя делить на ноль?
-- Потому-что при умножении на ноль чего угодно, получается всегда ноль.
-- Хватит капитанствовать! Умножение на ноль тут не причём! Почему делить нельзя?! Почему я 5 не могу поделить на ноль, и даже ноль не могу поделить????

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

К.О. не пишет книг. Извини.

Так почему же ты не ответишь на вопрос, если ответ очевиден?

я ответил уже. Тут хоть книгу напиши, ты всё равно не поймёшь. Смирись.

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

int& - это просто адрес.

здесь твоя ошибка: int& это не просто адрес. Просто адрес это int*. А int& это синтаксический сахар над разименованным просто адресом. Т.е. краткая запись *(int*), в которой временная переменная хранящая этот адрес часто выбрасывается компилятором. Потому ведёт себя ссылка на int как сам int. И потому из таких странных «типов» нельзя составить массив. Потому-что строго говоря — это и не тип, а просто сахар.

Ты же определяешь новый оператор для типа my_type, а не типа my_type&. Значит это всего лишь способ передачи, а не новый тип...

так и есть. Это просто синтаксический сахар для указателя.

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

Что-бы не ломать сишную функцию, которая возвращает число какого-то типа.

ref в этом смысле ничем не отличается от &

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

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

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

Ты ответил на другие вопросы. Которых я не задавал. Еще раз мой вопрос: почему в С++ решили ссылки объявить типом, хотя по факту он полноценным типом не является?

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

ref в этом смысле ничем не отличается от &

Тем, что & объявлен типом, хотя полноценным типом не является.

дык он и не объявлен никем «полноценным типом». Только в твоих фантазиях ссылка == полноценный тип.

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

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

её никто и не делал «полноценным типом». Кроме может тебя.

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

Еще раз мой вопрос: почему в С++ решили ссылки объявить типом, хотя по факту он полноценным типом не является?

ещё раз ответ: ссылки в C++ такие же «полноценные типы», как и массивы в сишечке. Просто сахар. С виду похоже на «ссылку» и на «массив». На самом деле, в обоих случаях — указатели.

emulek
()

Для однородности. Когда потребовались ссылки, в языке уже был механизм указателей. Вместо того, чтобы городить нечто чуждое языку, было решено сделать «новые» указатели. Недаром страуструп в раннем руководстве по языку пишет, что во многих случаях ссылка может заменить указатель, и рекомендует везде использовать ссылки.

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

здесь твоя ошибка: int& это не просто адрес. Просто адрес это int*. А int& это синтаксический сахар над разименованным просто адресом. Т.е. краткая запись *(int*), в которой временная переменная хранящая этот адрес часто выбрасывается компилятором. Потому ведёт себя ссылка на int как сам int. И потому из таких странных «типов» нельзя составить массив. Потому-что строго говоря — это и не тип, а просто сахар.

Это никак не относится к моему вопросу.

так и есть. Это просто синтаксический сахар для указателя.

Нет, не так. Проверь себя:

#include <iostream>

template<typename T>
void foo(T x)
{
    std::cout << "T\n";
}

template<>
void foo<const int &>(const int &x)
{
    std::cout << "const int&\n";
}

template<typename T>
void bar(T x)
{
     foo(x);
}

int main()
{
    int x;
    const int &r = x;
    std::cout << "foo\n";
    foo(x);
    foo(r);
    foo<int>(x);
    foo<int>(r);
    foo<const int &>(x);
    foo<const int &>(r);

    std::cout << "bar\n";
    bar(x);
    bar(r);
    bar<int>(x);
    bar<int>(r);
    bar<const int &>(x);
    bar<const int &>(r);
}

Просто синтаксическим сахаром был бы как раз ref. А ссылки еще являются отдельным типом.

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

рекомендует везде использовать ссылки.

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

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

& также ограничивает. Означает «синоним об'екта, но не задаёт его время жизни». Означает T * const с узкой семантикой и сахарным синтаксисом.

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

Они не являются «новыми указателями», они являются ..эм.. «синонимами». Тип ссылки, в отличие от указателя, не имеет собственных значений.

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

& также ограничивает

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

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

Вот валідный юзкейс где смысл ссылок проявляется хорошо. Хотя & всегда можно переделать на *

        struct UnlockGuard
        {
                UnlockGuard(mutex &mutex) : m_mutex(mutex) { m_mutex.unlock(); };
                ~UnlockGuard() { m_mutex.lock(); }
        private:
                UnlockGuard(const UnlockGuard &) = delete;
                UnlockGuard(UnlockGuard &&) = delete;
                UnlockGuard &operator=(const UnlockGuard &) = delete;
                UnlockGuard &operator=(UnlockGuard &&) = delete;

                mutex &m_mutex;
        };
dzidzitop ★★
()
Ответ на: комментарий от dzidzitop

Тут бы подошел указатель вполне. Кстати, на практике если ссылки хранить в качестве полей, то это чаще приводит к висячем ссылкам, чем при использовании указателей. Я уже не говорю о том, что ты сломал operator= для класса. Впрочем, все это не важно для конкретно этого примера.

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

Я уже не говорю о том, что ты сломал operator= для класса.

Я его запретил (он приведёт к exception).

Тут бы подошел указатель вполне.

Он всегда подошёл бы, разница только в том, что T& обозначает что-то конкретное, а не просто адрес участка памяти.

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