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

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #150 : Март 29, 2013, 04:48:12 pm »


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

Перекрыто что-то у тех кто будет в контейнерах храниться или нет - никого не волнует.
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера. Лично я stl не застал -96-98 годах когда приходилось использовать С++, фиг знает почему... отсюда и "странные" вопросы, в особенности  после того как осознал, что сам язык С++ довольно сильно изменился. :D

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #151 : Март 29, 2013, 05:41:11 pm »
Цитировать
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера.
Поясните, если не трудно. Я приводил исключительно факты, которые может проверить каждый. Нет никакой разницы, что у итератора есть оператор разыменования, возвращающий голый указатель, в результате чего ссылка принимает мусорное значение. Частным случаем мусорного значения является нулевой адрес. Зачастую всё гораздо хуже (адрес на стеке или временный блок памяти). Помимо прочего изменение контейнера делает итераторы неверными, но снова без каких-либо проверок времени исполнения. Иначе говоря, итератор, который хранит адрес блока памяти, узла списка или элемента карты и не является устойчивым или защищённым от легального вызова методов, это дырявый итератор. И в STL такие все.

Любой кривой указатель превращается в цивильную ссылку через разыменование. А постоянные отсылки к правильному коду (valid), в котором такие случаи невозможны, из той же оперы, что и просьба не совершать ошибок вообще.

P.S. Если проще, то ссылки не имеют права быть мусорными. А значит если std::vector на запрос элемента через оператор [] по описанию возвращает ссылку, то это должна быть ссылка или выброс исключения. Если итератор стал неверным, при попытке его использования должно тоже бросаться исключение. Ну а реальности всё так же, как и с ручным управлением памяти. Не совершайте ошибок, господа.

P.S.S Два оператора доступа к элементу ([], at) — это просто шутка разработчиков. Объект либо гарантирует безопасность своего использования, либо нет. А предоставление для ограниченного функционала специальной безопасной функции с выбивающимся из общего стиля синтаксисом — это яркий пример неудачного решения.

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #152 : Март 29, 2013, 07:04:01 pm »
Помимо прочего изменение контейнера делает итераторы неверными, но снова без каких-либо проверок времени исполнения.

Не всякого контейнера. Валидность итераторов и изменение контейнеров явно прописано в стандарте. Так что писать без UB можно, при желании ;)

Иначе говоря, итератор, который хранит адрес блока памяти, узла списка или элемента карты и не является устойчивым или защищённым от легального вызова методов, это дырявый итератор. И в STL такие все.

STL написана в расчете на эффективность (с точки зрения быстродействия). Дырявые итераторы (порой превращающиеся в голые указатели) - прямое следствие такого расчета. Вообще не вижу смысла перетирать еще раз про "безопасность vs эффективность". С++ традиционно небезопасный и быстрый. Безопасность в С++ достигается не низкоуровневыми средствами рантайма (типа GC), а высокоуровневыми под задачу (смарт поинтеры, ненулевые указатели и т.д.). При этом железных гарантий, естественно, нет (внизу голые поинтеры). Но как показывает практика при желании и высокоуровневых гарантий вполне хватает. В частности, STL - попытка подняться над поинтерами и массивами. И еще раз - без потери эффективности. Со своей задачей справляется неплохо, при умении пользоваться.

Любой кривой указатель превращается в цивильную ссылку через разыменование.

Кривой указатель не может превратиться в валидную ссылку. Что за фантазии такие? Кривой указатель - это UB и точка.

P.S. Если проще, то ссылки не имеют права быть мусорными.

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

А значит если std::vector на запрос элемента через оператор [] по описанию возвращает ссылку, то это должна быть ссылка или выброс исключения.

Это опять твои фантазии. Для [] прописан UB для невалидного индекса. Что он там возвращает в таком случае уже не важно.

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

Запили свою привильную STL. Кто мешает? Гарантировать валидность смартпоитера  в С++ можно. Так же как и всегда валидный итератор написать. Такая STL будет тормознее обычной и поэтому не очень востребованной. Нафига козе баян? Если можно просто взять другой безопасный язык?


valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #153 : Март 29, 2013, 07:11:52 pm »
"безопасный язык" - штука ну о-очень относительная. Вот скажем java, c# не являются безопасными языками с точки зрения haskell'я :-)

А еще понятие безопасности зависит от задачи. Если у меня жесткий реалтайм, то какой-нибудь чертов Си или Асм будет более безопасен чем haskell ;-)

Впрочем, "безопасность по памяти" в C++11 включена же:
http://en.cppreference.com/w/cpp/memory/gc/pointer_safety
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #154 : Март 29, 2013, 07:14:47 pm »
Цитировать
спасибо, это исчерпывающий ответ, ставящий крест на рассуждениях Берсеркера.
Поясните, если не трудно.
Да , конечно - мы друг друга не  понимаем... в данном случае крест ставится на "легитимности" рассуждений (но разумеется это имхо)... тут все просто.. если разименование итератора это ОК. то ваши рассуждения были бы легитимными в том случае если бы операция разименования не перекрывалась в итераторах , а раз это не так (пусть даже если это перекрытие будет чисто декларативным - то есть по факту не наблюдаемым в коде) - ваши действия есть  чистый хак (или эксплоит , если вам это больше нравится), и рассуждения могут оказаться неверными в следующей реализации STL. То есть смотрите... я говорю  про то, что ваши рассуждения имеют очевидную уязвимость ВНЕ ЗАВИСИМОСТИ  от того  насколько они верны в приведенных вами примерах....

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #155 : Март 29, 2013, 07:33:04 pm »
Дизер, я вовсе не полагаюсь на особенности реализации при написании кода. Так что в этом вопросе у нас разногласий нет.

Алексей, хочу заметить, что никаких волшебных опций или макросов для контроля безопасности STL нет.

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

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #156 : Март 29, 2013, 07:37:42 pm »
Дизер, я вовсе не полагаюсь на особенности реализации при написании кода. Так что в этом вопросе у нас разногласий нет.

если это так - то зачем вообще приводить было этот пример....

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #157 : Март 29, 2013, 07:40:31 pm »
Алексей, хочу заметить, что никаких волшебных опций или макросов для контроля безопасности STL нет.
В стандарте - нет. В конкретных реализациях - бывает.

Про контроль безопасности по памяти, я в предыдущем посте дал ссылку. Это в стандарте есть безотносительно stl.

И да, я считаю решение с [] и at() в векторе абсолютно адекватным. Неоднократно пользовался и тем и этим (и в виде смеси) и очень был доволен.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #158 : Май 24, 2013, 09:54:29 pm »
Размышляя на досуге, пришёл к выводу, что большинство типов указателей, как существующих, так и новых, можно описать при помощи ограниченного набора атрибутов. Набор представлен ниже:

const — указатель не изменяемый, инициализация по месту объявления или в качестве аргумента функции;
no_addr — запрещает применение операции получения адреса к указателю; Используется для повышения возможностей оптимизатора.
auto_deref — упоминание идентификатора указателя равнозначно его разыменованию на месте "(*p)". Включает в себя no_addr. Для установки значения самого указателя используется специальный синтаксис (ключевое слово let, инициализация при объявлении в С++, аргумент функции);
fragile — "ломкий". Указатель на данные подлежащие скорому удалению. Семантика перемещения в С++;
not_null — гарантированно указывает на данные, проверка времени компиляции;
null_guard — проверка на неравенство нулевому указателю во время исполнения (в аргументах функций, при присвоении указателей без атрибута null_guard или not_null).

Теперь составим известные нам типы указателей из Ады, Паскаля, Оберона и С++ (pointer — просто указатель):

1) Указатель, к которому нельзя применить операцию взятия адреса:
Код: (cpp) [Выделить]
typedef no_addr pointer ptr;
int x;
int ptr px = &x; // px = @x
&px // error
*px = 7; // можно оптимизировать до x = 7, px выбросить вообще

2) Автоматически разыменовываемый указатель
Код: (cpp) [Выделить]
typedef auto_deref pointer ref;
int x;
int ref px; // px = @???
px = 1; // possible run-time crash
let px = &x; // px = @x
px = 1; // x = 1

3) Гарантированно указывающий на что-либо указатель
Код: (cpp) [Выделить]
typedef not_null pointer ptr;
int x, y;
int ptr p; // compile time error
int ptr p = &x; // p = @x
p = &y; // p = @y
*p = 1; // y = 1

4) Гарантированно ненулевой указатель, проверка времени исполнения
Код: (cpp) [Выделить]
typedef null_guard pointer ptr;

void Test (int ptr p) {}
iTest(nullptr); // run-time error

5) Семантика перемещения — функция принимает аргументом указатель на объект, который будет следом удалён
Код: (cpp) [Выделить]
typedef fragile pointer ptr;
void Test (TObject ptr obj) {}
int *p = new int();
Test(p);
delete p;

6) VAR-параметр Оберона, Паскаля, Ады
Код: (delphi) [Выделить]
typedef const auto_deref not_null pointer var;
procedure Test (var x: integer);
Test(1); // compile-time error
Test(x); // ok
Test(p^); // ok if p is pointer to integer

7) not null аргумент Ады, проверка времени исполнения
Код: (delphi) [Выделить]
typedef not_null pointer ptr;
procedure Test (x: ptr integer);
Test(nil); // compile-time error
Test(@x); // ok
Test(p); // ok

8) Классические ссылки в С++
Код: (cpp) [Выделить]
typedef const not_null auto_deref pointer &;
Примечание: возможность иметь ссылки на константы или возвращаемые по значению объекты есть синтаксический сахар, который можно с тем же успехом применить к любым пользовательским указателям в новой модели:

Код: (cpp) [Выделить]
const int* p = 1; // p = @c1, c1 is const int, c1 = 1
TObject Test () {}
TObject* obj = Test(); // obj = @local1, local1 is TObject, @local1 передаётся функции теневым аргументом

Что думаете, господа?

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #159 : Май 24, 2013, 09:57:59 pm »
Что думаете, господа?
Я глубоко в написанное не вникал, поэтому что думаю сказать не могу. Зато могу посоветовать прочесть про типы "указателей" в Rust'e. Там нечто подобное вроде бы как раз и сделали.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #160 : Май 24, 2013, 10:19:05 pm »
Пришло время таки взглянуть на Rust.

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #161 : Май 24, 2013, 10:27:34 pm »
Не похоже:

Цитировать
8.1.8 Pointer types

All pointers in Rust are explicit first-class values. They can be copied, stored into data structures, and returned from functions. There are four varieties of pointer in Rust:
Managed pointers (@)

These point to managed heap allocations (or "boxes") in the task-local, managed heap. Managed pointers are written @content, for example @int means a managed pointer to a managed box containing an integer. Copying a managed pointer is a "shallow" operation: it involves only copying the pointer itself (as well as any reference-count or GC-barriers required by the managed heap). Dropping a managed pointer does not necessarily release the box it points to; the lifecycles of managed boxes are subject to an unspecified garbage collection algorithm.
Owning pointers (~)

These point to owned heap allocations (or "boxes") in the shared, inter-task heap. Each owned box has a single owning pointer; pointer and pointee retain a 1:1 relationship at all times. Owning pointers are written ~content, for example ~int means an owning pointer to an owned box containing an integer. Copying an owned box is a "deep" operation: it involves allocating a new owned box and copying the contents of the old box into the new box. Releasing an owning pointer immediately releases its corresponding owned box.
Borrowed pointers (&)

These point to memory owned by some other value. Borrowed pointers arise by (automatic) conversion from owning pointers, managed pointers, or by applying the borrowing operator & to some other value, including lvalues, rvalues or temporaries. Borrowed pointers are written &content, or in some cases &f/content for some lifetime-variable f, for example &int means a borrowed pointer to an integer. Copying a borrowed pointer is a "shallow" operation: it involves only copying the pointer itself. Releasing a borrowed pointer typically has no effect on the value it points to, with the exception of temporary values, which are released when the last borrowed pointer to them is released.
Raw pointers (*)

Raw pointers are pointers without safety or liveness guarantees. Raw pointers are written *content, for example *int means a raw pointer to an integer. Copying or dropping a raw pointer is has no effect on the lifecycle of any other value. Dereferencing a raw pointer or converting it to any other pointer type is an unsafe operation. Raw pointers are generally discouraged in Rust code; they exist to support interoperability with foreign code, and writing performance-critical or low-level functions.