Существует два синтаксических подхода к доступу к объектам, созданным динамически (в куче): через ссылки и через указатели. Обращение через ссылку неотличимо от непосредственного обрашения к объекту, обращение через указатель явно отличается (присутствует явное разыменование).
В C# и Java используются ссылки, при этом введено соглашение что все объекты классов передаются по ссылке, а структуры и переменные примитивных типов — по значению. Там невозможно создать класс не стеке.
В C, Go, Rust используются явные указатели и явное размыенование. То есть синтаксис косвенного обращения — через указатель — всегда отличается от синтаксиса прямого обращения к стековому или глобальному объекту. В С++ сосуществуют и указатели и ссылки, причем поведение ссылок отличается от такового в C#.
В чем по-вашему преимущества и недостатки обоих подходов? Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)? Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Здравствуйте, x-code, Вы писали:
XC>В чем по-вашему преимущества и недостатки обоих подходов?
Ни в чем: механически это один и тот же маханизм. Отличия в синтаксисе и возлагаемой в C++ на ссылки семантике.
XC>Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Думаю правильно, хотя синтаксис, хоть и уже привычен, тем не менее не радует.
В C# т.н. семантика типа (ссылочная/значимый) связана с самим типом, а в C++ все типы значимые, с возможностью превратить его в ссылку (кое-какая работа с указателями в C# слава богу есть), строго говоря это отдельный тип.
Так вот, мне видится, что гораздо удобнее иметь весь арсенал в своем расположении, а не городить костыли вокруг урезанных возможностей.
В жизни, мы как раз и хотим иногда рассматривать один и тот же тип то как значение, то оперировать ссылкой на него, а иногда и вовсе прибегнуть к обычным указателям (хотя ссылка/указатель здесь все же про C++, я могу представить язык в котором это объединено). Тем не менее, возможно, иметь в наличие обычные старые указатели это хорошо, просто с точки зрения что они привычны и понятны большому числу людей.
Здравствуйте, x-code, Вы писали:
XC>В чем по-вашему преимущества и недостатки обоих подходов? Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)? Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Указатели — это гибкость, ссылки — безопасность (относительная).
В остальном — чисто синтаксическая вкусовщина.
Здравствуйте, x-code, Вы писали:
XC>Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)?
Забыл добавить, я бы предпочел везде точку, но если есть понятие указателя — тогда стрелку. Сигнатура должна быть понятной (явной).
А вот, если, компилятор/язык сможет сам решать где нужно передавать по ссылке или по значению — тогда неявно.
В тоже время, мне жутко не нравятся доброусыпанные крючки, и опыт шарпа показывает, что здесь вполне возможен разумный компромис. Т.е., если бы был хороший и удобный вариант передачи этого неявно с возможностью указать явно — это было бы интересно.
Здравствуйте, x-code, Вы писали:
XC>Существует два синтаксических подхода к доступу к объектам, созданным динамически (в куче): через ссылки и через указатели. Обращение через ссылку неотличимо от непосредственного обрашения к объекту, обращение через указатель явно отличается (присутствует явное разыменование).
XC>В C# и Java используются ссылки, при этом введено соглашение что все объекты классов передаются по ссылке, а структуры и переменные примитивных типов — по значению. Там невозможно создать класс не стеке.
XC>В C, Go, Rust используются явные указатели и явное размыенование.
Ну по крайней мере для Rust это не так.
В Rust есть и ссылки и указатели.
Здравствуйте, x-code, Вы писали:
XC>В C, Go, Rust используются явные указатели и явное размыенование. То есть синтаксис косвенного обращения — через указатель — всегда отличается от синтаксиса прямого обращения к стековому или глобальному объекту. В С++ сосуществуют и указатели и ссылки, причем поведение ссылок отличается от такового в C#.
В Go для доступа к полю структуры всегда используется точка, независимо от того, что стоит слева от точки: имя переменной-структуры, или указатель на структуру.
XC>В чем по-вашему преимущества и недостатки обоих подходов? Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)? Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
На мой взгляд, какого-нибудь одного способа вполне бы хватило. Проблема C++ в том, что из него ничего нельзя выкинуть из-за совместимости со всеми предшествующими версиями данного языка.
Здравствуйте, x-code, Вы писали: XC>Существует два синтаксических подхода к доступу к объектам, созданным динамически (в куче): через ссылки и через указатели.
В некоторых языках по ссылке и по указателю можно обращатся к объектам созданным на стеке, поэтому существует три синтаксических способа доступа к объектам: по имени, через указатель, по ссылке. XC>Обращение через ссылку неотличимо от непосредственного обрашения к объекту, обращение через указатель явно отличается (присутствует явное разыменование).
Общее положение дел несколько сложнее. Вообще говоря ссылка от указателя отличается только тем, что указатель можно сравнить с нуль-объектом. Это все различия. XC>В чем по-вашему преимущества и недостатки обоих подходов?
В том контексте, в котором вы задаёте вопрос я вижу разницу исключительно терминологическую. XC>Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)?
Для всякого вопроса об "лучшести" нужно задать критерий этой "лучшести" и тогда ответ может быть найден. XC>Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
В С++ ситуация намного сложнее описываемой дихотомии сразу по многим аспектам.
Во-первых, С++ может использоваться для прямого доступа к памяти по физическому адресу. Это обеспечивается с помощью указателей (хотя можно придумать и другой способ, если иметь возможность размещать обычную переменную по заданному адресу) и это объясняет наличие указателей в языке.
Во-вторых, в С++ есть арифметика на указателях, которая непосредственно связана с предыдущем пунктом.
В-третьих, идеологически голые указатели в С++ не нужны, кроме как для доступа по физическим адресам. Во всех других случаях достаточно использовать объекты, например, типа "Умные указатели", которые по некоторым признакам не указатели, а объекты.
В-четвёртых, доступ по '.' через '->' — это доступ с помощью операторов и оператор '->' может применятся к обычному объекту, а не указателю.
Скрытый текст
class A
{
public:
A* operator -> ()
{
if ( m_value < 0 )
throw std::out_of_range("must be positive");
return this;
}
int Get()
{
return m_value;
}
private:
int m_value = 1;
};
int main()
{
A a;
int n1 = a->Get(); // check valueint n2 = a.Get(); // direct access
std::cout << n1 << ", " << n2 << std::endl;
return 0;
}
В частности, есть попытки Страуструпа и других здесь разрешить перегрузку оператора '.'. Если это будет сделано, то синтаксическая разница междц указателем и объектом полностью размоется.
Здравствуйте, x-code, Вы писали:
XC>В чем по-вашему преимущества и недостатки обоих подходов? Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)? Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Различение способов доступа создает на ровном месте трудности для написания обобщенного/генерик кода. Когда доступ везде через точку, то такую функцию
auto sumAB(T)(T obj) {
return obj.a + obj.b;
}
я могу использовать с любым классом и структурой, где есть подходящие поля a и b.
struct S {
int a, b;
}
class C {
double a, b, c;
this() { a = 1; b = 2; c = 3; }
}
void main() {
auto s = S(3,4);
auto c = new C();
sumAB(s).writeln;
sumAB(c).writeln;
}
Или даже без генериков, просто если в каком-то коде я решу заменить класс структурой или наоборот, модификаций кода потребуется намного меньше, чем если б нужно было еще точки на стрелки менять. Как обычно, в С++ было неправильно, в D сделали правильно.
Здравствуйте, x-code, Вы писали:
XC>Существует два синтаксических подхода к доступу к объектам, созданным динамически (в куче): через ссылки и через указатели. Обращение через ссылку неотличимо от непосредственного обрашения к объекту, обращение через указатель явно отличается (присутствует явное разыменование).
XC>В C# и Java используются ссылки, при этом введено соглашение что все объекты классов передаются по ссылке, а структуры и переменные примитивных типов — по значению. Там невозможно создать класс не стеке.
XC>В C, Go, Rust используются явные указатели и явное размыенование. То есть синтаксис косвенного обращения — через указатель — всегда отличается от синтаксиса прямого обращения к стековому или глобальному объекту. В С++ сосуществуют и указатели и ссылки, причем поведение ссылок отличается от такового в C#.
XC>В чем по-вашему преимущества и недостатки обоих подходов? Что лучше — явность (однозначно виден способ передачи объекта в функцию, способ обращения к нему и т.д.) или унификация доступа (везде точка, никаких разыменований)? Какой подход вам больше нравится? Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Здравствуйте, wamaco, Вы писали:
W>мне нравится, как сделано в Pascal
В виртовском Паскале, в Object Pasccal из Turbo/Borland Pascal или в Паскале из Delphi? Если мне не изменяет мой склероз, там как раз это сделано по разному.
BFE>Во-первых, С++ может использоваться для прямого доступа к памяти по физическому адресу. Это обеспечивается с помощью указателей
Поправлю.
В общем случае физический адрес скрыт внутри MMU и недоступен приложению. Прикладной софт оперирует исключительно виртуальными адресами.
Устройства без MMU это, в основном, простейшие микроконтроллеры + Bare Metal.
BFE>Во-вторых, в С++ есть арифметика на указателях, которая непосредственно связана с предыдущем пунктом. BFE>В-третьих, идеологически голые указатели в С++ не нужны, кроме как для доступа по физическим адресам.
Арифметика указателей удобна для работы с массивами объектов. Даже без привязки к смыслу значения указателя, пусть он хоть в попугаях будет.
Здравствуйте, IID, Вы писали:
BFE>>Во-первых, С++ может использоваться для прямого доступа к памяти по физическому адресу. Это обеспечивается с помощью указателей
IID>Поправлю. IID>В общем случае физический адрес скрыт внутри MMU и недоступен приложению.
Зависит от того, что называть общим случаем.
IID>Прикладной софт оперирует исключительно виртуальными адресами.
"Прикладной" софт бывает разный.
IID>Устройства без MMU это, в основном, простейшие микроконтроллеры + Bare Metal.
Как раз из этого уровня "пришли" указатели в C, а потом и в С++.
IID>Арифметика указателей удобна для работы с массивами объектов. Даже без привязки к смыслу значения указателя, пусть он хоть в попугаях будет.
Для двумерных (и большей размерности) массивов арифметика указателей настолько неудобна, что практически никогда не используется с указателями.
В современном С++ арифметика указателей используется с особыми объектами, называемыми итераторами, которые совсем не указатели на объекты, а указатели на положение в структуре данных. Поэтому итераторы имеют больший уровень абстракции, чем уровень абстракции указателей. Например, итератор может перебирать байты всех файлов лежащих в каталоге с помощью функции std::find. Такой итератор, конечно, можно называть указателем, вот только от обычного указателя итератор отличается весьма.
XC>почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
Сложилось так еще в PL/1 в 1967 году.
Так как можно было задать указатель в описании, но при любом обращении «перевесить» указатель явно на любой другой:
declare
a (100) float based(p1),
(p1,p2) ptr;
allocate a;
a=0; // ссылка
p2=p1;
p2->a=0; // точно такой же эффект
никаких особых проблем это не создавало, наоборот «в теле такая приятная гибкость образовалась…» (с) м/ф «Падал прошлогодний снег»
Здравствуйте, x-code, Вы писали:
XC> Правильно ли то, что в С++ сосуществуют оба способа, почему так сложилось, дает ли это какие-то преимущества и создает ли это какие-либо проблемы?
В С++ ссылки и указатели имеют несколько разную семантику для присваивания: ptr1 = ptr2 — присваивание "одного указателя другому", ref_1 = ref_2 — присваивание "значения, на которое ссылается вторая ссылка, значению, на которое ссылается первая".