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

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #120 : Март 28, 2013, 01:28:58 pm »
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.

Гыыы... вот до чего можно дойти изучая С++ (разные UB  - однако - вдумайтесь )- ;D

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #121 : Март 28, 2013, 02:01:46 pm »
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.

Гыыы... вот до чего можно дойти изучая С++ (разные UB  - однако - вдумайтесь )- ;D
Ну, а чего удивительного? Если сущность едина, то наверно UB должно как-то одинаково быть. Хотя конечно не фа-акт. :-) Впрочем UB это не единственный вид забавных всяких B. Щща отдельную тему под это дело заведу.
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #122 : Март 28, 2013, 02:07:50 pm »
Если сущность едина, то наверно UB должно как-то одинаково быть.
Едино только понятие UB, а кто кому чего должен - без понятия... :)

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #123 : Март 28, 2013, 03:19:02 pm »
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.

Разыменование нулевого указателя - это невалидный код. Уже много раз написали. Что там будет после этого кода (нулевая ссылка) - уже неинтересно. А вообще find на пустой коллекции вернет не NULL, а end() - который скорее всего будет совсем не NULL и разыменование такого итератора скорее всего обрушит дебаг сборку.

Ненулевость ссылок чисто декларативное свойство, а не средство отладки. Представь, что ты написал библиотеку с паблик функцией:
void f(params const* p)
{
    assert(p);
    ...
}

И в документации написал, что в функцию f() передаются параметры, которые не могут быть NULL. Пользователь твоей библиотеки не читает документацию, и передается туда NULL в надежде, что функция может работать с какими-то дефолтовыми параметрами. Все крэшится, нерадивый пользователь лезет читать документацию, передает валидный указатель, компилирует, запускает...

Теперь представь, что ты изначально написал:
void f(params const& p)
{
    // assert теперь не нужен, если тебе пришла нулевая ссылка, то значит в вызывающем коде есть UB, твой код абсолютно корректный
    ...
}
Пользователь твоей библиотеки по-прежнему не читает документацию, но NULL в твою функцию уже не отдаст (если пишет программы без UB). Все в выигрыше - тебе меньше ассертов писать и документации, пользователю меньше читать документации.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #124 : Март 28, 2013, 03:55:40 pm »
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.

Разыменование нулевого указателя - это невалидный код. Уже много раз написали. Что там будет после этого кода (нулевая ссылка) - уже неинтересно. А вообще find на пустой коллекции вернет не NULL, а end() - который скорее всего будет совсем не NULL и разыменование такого итератора скорее всего обрушит дебаг сборку.
Чуть поясню:

end() вообще нельзя разименовывать (по стандарту), это раз. Его разименовывание это UB.

Два: "end() returns an iterator which is the past-the-end value for the container", даже если у нас итератор в данном конкретном случае вдруг является указателем (что бывает например в случае массивов), то элемент следующий за последним явно будет иметь не нулевой адрес, не nullptr совсем.

Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор. Если мы итератор начальный получаем через begin(), то begin() на пустом контейнере вернет end() (согласно пункту 7 на стр 541: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf).

Соответственно имеем find(end(vec),end(vec)). Очевидно, что контейнер ака скажем вектор, вовсе не обязан быть пустой :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #125 : Март 28, 2013, 05:38:55 pm »
Цитировать
просто по тому, что  так ПОСТАНОВИЛИ
Именно так. Создатель языка взял идею из Алгола и урезал по субъективным причинам.
Исправление могло выглядеть как "let reference = new_object;"

Цитировать
Во-первых: я явно привел пример когда видно, что внутренняя реализация ссылок и указателей может быть различна - в случае gcc у них разные UB в случае const_cast'a.
Во-вторых: аргументы влада.
В-третьих: ссылка гарантирует что переменная валидна. Никаких нулей.
Алексей, смотрите. Использование указателя может транслироваться в:
1) Ничего. Оптимизация при известном адресе значения.
2) Копию константного объекта в виде временной переменной.
3) В полях структур, в сложных выражениях, в результатах невстроенныхфункций — в указатель.

А если проще: без указателей реализовать поддержку ссылок нельзя. Без оптимизаций, сугубо на указателях можно. С этим-то все согласны?

Если разрешить переустанавливать ссылки, то 3-й пункт будет использоваться чуток чаще. При чём анализ потока управления происходит и сейчас. При совершении прыжка назад (goto) ссылка изменяется, а следовательно компилятор ничего не оптимизирует и в лоб выделяет место под неё.

Для гарантии не-NULL нужны проверки времени исполнения, иначе это не гарантии.

Цитировать
Я не очень понимаю различие между реализацией и, гм, оптимизацией реализации :-)
Оптимизация в стандарте не прописана, исходите из предположения её полного отсутствия.

Цитировать
В принципе полагаю возможно как минимум часть семантики (если не всю семантику) ссылок реализовать через простое копирование объекта (без вызовов конструкторов, тупо memcpy). И это не будет противоречить стандарту.
В лоб нельзя. Просто пара причин даже для этого ограниченного случая: goto разрешён (нужно проверять, устанавливается ли ссылка один раз), структурам и стековым кадрам нельзя жиреть (не будете же вы копировать по 5 КБ на объект), объявление указателей/ссылок возможно до полного определения структур.  Поправьте, если где ошибаюсь.

Цитировать
Эмм.. Что за домыслы? Иммутабельность естественно имеет смысл в императивных языках. Функциональность довольно таки ортогональна иммутабельности :-) Впрочем, задам ка я вопрос - какое основное отличие функционального языка от императивного? Вот прям основное-основное.
Исправляюсь. Императивным языкам не свойственна навязанная неизменность. То есть дело не в том, чтобы объявлять саму ссылку const или нет, а в том, что она всегда константная. Возьмите указатели и примените к ним то же решение: указатели всегда константные. Что такое решение даёт программисту?

Цитировать
Э? При чем тут реализация?
MemoryFS mem_fs;
FileFS file_fs;

AnyFS &fs = file_fs;
...
if (some condition) let fs = file_fs; // такой "фичи" нет

Цитировать
Кто сказал что псевдонимы могут быть только детерминированными? :-)
Для меня есть два понятия: указатель на динамическую величину или псевдоним статической. То есть в случае псевдонима мы ещё на этапе компиляции знаем, к чему привязываемся. Но это субъективное восприятие, на дискуссии не настаиваю, да и ценность этого восприятия мала.

Цитировать
Покажи пожалуйста, ну, например на примере java, как ты собираешься бороться с нулями в изменяемых ссылках. То есть чтобы гарантированно, и не зависило от кривости рук программера.

Ну, например на том же примере твоем: vector<int> &a = rand() % 100 >= 50 ? b : c;
Пусть тут будет: 1) if..else. 2) не будет лишних инициализаций.
А мы не отменяем необходимость инициализации при объявлении.

Object &a = b;
...
let a = c;
...
let a = d;

где let — установка указателя, а не изменение значения объекта, на которого ссылаются.

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

Не редко вижу в ответах фразы "In fact, vectors iterators are usually implemented as pointers.", но не проверял итераторы в исходниках STL. Хотя более чем уверен, либо просто указатель, либо структура из одного поля с набором требуемых inline-операторов.

Цитировать
А то что ты описал - не будет NULL-ссылкой. Будет UB чистой воды (причем по двум разным причинам одновременно). И нет, это не корректный код. :-)
Неопределённое поведение в стандарте не равно неопределённому поведению в реальности. В моей реальности программисты ошибаются, программы не идеальны, а некорректные ссылки не рушат программы до попытки обращения к объектам.

Цитировать
Ненулевость ссылок чисто декларативное свойство, а не средство отладки. Представь, что ты написал библиотеку с паблик функцией:
Согласен. Но я ни разу не предлагал разрешить ссылкам быть нулевыми. Я предлагал вернуть им возможность указывать на разные существующие объекты.

Цитировать
end() вообще нельзя разименовывать (по стандарту), это раз. Его разименовывание это UB.
Знаю. Но поскольку «нельзя» относится к тем же нельзя, что и «нельзя писать несовершенный код», то в реальности где-то будет опущена проверка результата.

Цитировать
то элемент следующий за последним явно будет иметь не нулевой адрес, не nullptr совсем.
begin() + size() он будет. begin() вернёт nullptr, а size() будет 0.

Цитировать
Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор.
Ну да, будет возвращён end(), который есть nullptr.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #126 : Март 28, 2013, 06:02:53 pm »
begin() + size() он будет. begin() вернёт nullptr, а size() будет 0.

Цитировать
Три - чтобы запустить find на "пустой коллекции" нужно получить вначале начальный и конечный
итератор.
Ну да, будет возвращён end(), который есть nullptr.

А если подумать головой? :-)
#include <iostream>
#include <vector>

int main() {
std::vector<int> a;
a.reserve(42);
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
                  << "\tend: " << (size_t)&*a.end() << std::endl;
return 0;
}

Вывод:
is empty: 1
begin: 4388292592 end: 4388292592

Контейнер пуст, но при этом begin и end не нуль.

PS. Кстати, итераторы не являются указателями, поэтому пришлось писать такое, чтобы получить адрес: (size_t)&*a.begin()

PPS. И кажется я писал, что для твоего примера не нужно пустого контейнера, достаточно в find скормить два end'а произвольного контейнера. find'у вообще пофиг на контейнер.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #127 : Март 28, 2013, 06:12:57 pm »
Зачем жульничаете?

is empty: 1
begin: 0        end: 0

std::vector<int> a;
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
<< "\tend: " << (size_t)&*a.end() << std::endl;

И там именно указатели на выделенную память в ваши 42 элемента. Уберите reserve и увидите то, о чём я писал.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #128 : Март 28, 2013, 06:20:36 pm »
Зачем жульничаете?

is empty: 1
begin: 0        end: 0

std::vector<int> a;
std::cout << "is empty: " << a.empty() << std::endl;
std::cout << "begin: " << (size_t)&*a.begin()
<< "\tend: " << (size_t)&*a.end() << std::endl;

И там именно указатели на выделенную память в ваши 42 элемента. Уберите reserve и увидите то, о чём я писал.
Там нет ни одного элемента - empty дает true, это железно означает что в векторе нет НИ ОДНОГО элемента. А то что оно там что-то закешировало - это дело десятое. То же самое может получиться после серии вставок и удаления элементов.

Впрочем, оставим вектор в покое. Возьмем map:
#include <iostream>
#include <map>
int main() {
std::map<int,int> m;
std::cout << "is empty: " << m.empty() << std::endl;
std::cout << "begin: " << (size_t)&*m.begin()
                  << "\tend: " << (size_t)&*m.end() << std::endl;
return 0;
}

is empty: 1
begin: 140734916160464 end: 140734916160464

Так что там про нулевость end'а в случае пустого контейнера?
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #129 : Март 28, 2013, 06:25:48 pm »
Список:
#include <iostream>
#include <list>
int main() {
std::list<int> m;
std::cout << "is empty: " << m.empty() << std::endl;
std::cout << "begin: " << (size_t)&*m.begin()
                  << "\tend: " << (size_t)&*m.end() << std::endl;
return 0;
}

is empty: 1
begin: 140734989089752 end: 140734989089752

PS. Ощущаю себя прям таки разрушителем легенд :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

DddIzer

  • Гость
Re: C++ инициализация ссылок
« Ответ #130 : Март 28, 2013, 06:37:15 pm »

PS. Ощущаю себя прям таки разрушителем легенд :-)
я не улавливаю другое... зачем  Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...

valexey_u

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

PS. Ощущаю себя прям таки разрушителем легенд :-)
я не улавливаю другое... зачем  Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...
Не-не-не, разименовывание итератора дает тебе собственно объектик который соответствует данному итератору (но естественно не дает тебе собственно тушку самого итератора, тушка итератора - это итератор и есть). Это абсолютно нормально и правильно.

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

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #132 : Март 28, 2013, 06:44:09 pm »
И снова жульничаете. Я же сказал, для контейнеров со сплошным блоком памяти. Списки определяются как классические двусвязные. Вы видите на консоли адрес на стеке (это много хуже, чем NULL, кстати).

Вот код для вектора из STL:

      // iterators
      /**
       *  Returns a read/write iterator that points to the first
       *  element in the %vector.  Iteration is done in ordinary
       *  element order.
       */
      iterator
      begin() _GLIBCXX_NOEXCEPT
      { return iterator(this->_M_impl._M_start); }

      /**
       *  Returns a read-only (constant) iterator that points to the
       *  first element in the %vector.  Iteration is done in ordinary
       *  element order.
       */
      const_iterator
      begin() const _GLIBCXX_NOEXCEPT
      { return const_iterator(this->_M_impl._M_start); }

      /**
       *  Returns a read/write iterator that points one past the last
       *  element in the %vector.  Iteration is done in ordinary
       *  element order.
       */
      iterator
      end() _GLIBCXX_NOEXCEPT
      { return iterator(this->_M_impl._M_finish); }

Для списка:

      // iterators
      /**
       *  Returns a read/write iterator that points to the first element in the
       *  %list.  Iteration is done in ordinary element order.
       */
      iterator
      begin() _GLIBCXX_NOEXCEPT
      { return iterator(this->_M_impl._M_node._M_next); }

      /**
       *  Returns a read-only (constant) iterator that points to the
       *  first element in the %list.  Iteration is done in ordinary
       *  element order.
       */
      const_iterator
      begin() const _GLIBCXX_NOEXCEPT
      { return const_iterator(this->_M_impl._M_node._M_next); }

struct _List_node_base
    {
      _List_node_base* _M_next;
      _List_node_base* _M_prev;


Цитировать
я не улавливаю другое... зачем  Берсеркеру разименовывать итераторы... а х ..да едрить.. соображения "эффективности"...
Разыменование является нормальным явлением.

DddIzer

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

Разыменование является нормальным явлением.
и итераторов в частности?

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #134 : Март 28, 2013, 06:51:00 pm »
И снова жульничаете. Я же сказал, для контейнеров со сплошным блоком памяти. Списки определяются как классические двусвязные. Вы видите на консоли адрес на стеке (это много хуже, чем NULL, кстати).
Не понял, где ты что-то писал про сплошные блоки памяти?
Кстати, те же итераторы сделаны через указатели. Не сложно вызвать какой-нибудь find на пустой коллекции, разыменовать его и привязать объект к ссылке. Будет чистой воды NULL-ссылка в самом что ни на есть корректном коде.
Итого ограничения: пустой контейнер (то есть empty() выдает true), и вызов для него find. Пустой map, list и vector (у которого что-то там унутре зарезервировано) -- вполне подходят.

PS. А адрес чего именно вижу - стека, или кучи, или вообще статической памяти - глубоко пофиг до тех пор, пока реализация контейнера соответствует стандарту. Ничто не мешает написать реализацию vector'а которая будет в некоторых случаях располагать свои элементы на стеке, более того, вектор и вообще стандартная либа может быть ВШИТА в компилятор, и соответственно реализовываться средствами самого компилятора, не имея исходников как таковых.
Y = λf.(λx.f (x x)) (λx.f (x x))