Почему невозможно иметь ссылку на пустоту? - PullRequest
36 голосов
/ 19 января 2009

Почему невозможно иметь ссылку на void? Единственная вещь, которую я нашел в Стандарте C ++, это строка в 8.3.2.1

Декларатор, который указывает тип "ссылка на cv void", имеет неправильную форму.

Почему это так? Почему я не могу написать «универсальную» функцию, которая принимает void&?

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


Чтобы пояснить немного, я понимаю, что использование ссылки на void "как есть" будет таким же бессмысленным, как разыменование указателя на void. Тем не менее, я мог бы привести его к ссылке на sometype , чтобы использовать его, не так ли? На самом деле, я не понимаю, почему следующий фрагмент может работать ...

void foo(void *data)
{
    int *i = reinterpret_cast<int*>(data);
    // do something with i
}

... а этот не может:

void foo(void &data)
{
    int &i = reinterpret_cast<int&>(data);
    // do something with i
}

Ответы [ 10 ]

37 голосов
/ 19 января 2009

Если бы у вас была ссылка на void, что бы вы сделали с ней? Это не будет число, или символ, или указатель, или что-то в этом роде. Ваша гипотетическая универсальная функция не может выполнить с ней никаких операций, кроме как с получением ее адреса (а не ее размера).

«void» имеет два применения: отказаться от любых знаний о типе (как в void *) и ничего не указывать в противоположность чему-либо (возврат void функции). Ни в том, ни в другом случае невозможно сказать что-либо о пустом месте, кроме того, что оно может иметь адрес.

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

14 голосов
/ 19 января 2009

Сначала спросите себя, как бы вы отменили ссылку на пустой указатель?

void *p = /*something*/ ;
cout << *p << endl;

Приведенный выше код не имеет смысла, одна из причин, по которой мы упустили это, заключается в том, что мы можем сказать: «Мне нужно сделать какую-то общую работу с указателем, и я не знаю и не беспокоюсь о том, на что я указываю». По определению компилятор не знает, на что указывает пустота *, поэтому он не может разыменовать его. Вы можете - путем приведения - но компилятор не может.

Ссылка на void страдает от той же проблемы, по определению указанные данные не имеют типа, поэтому на них нельзя ссылаться каким-либо значимым образом.

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

Не уверен, объяснил ли я это так хорошо, как хотел.

Рубен, есть мысли?

РЕДАКТИРОВАТЬ: Чтобы ответить на ваши изменения.

Возьмите первую функцию, где вы передаете void * данные. данные - это абсолютно правильный элемент, вы можете с ним вычислять, или, если у вас реализовано какое-либо ведение журнала, вы можете зарегистрировать его.

logger << data;

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

void* data = /* some assignment */;
logger << *data; // compiler error.

Теперь компилятор не позволит вам разыменовать void * по любой причине (это не имеет смысла), то же самое относится и к ссылке на void & data, за исключением того, что, поскольку это ссылка , она неявно разыменовывается все время . Компилятор не позволит вам разыменовать пустоту * в одной операции, он не позволит вам разыменовать ее постоянно.

void& data = /* some assignment *.;
logger << data; // means same as logger << *data above

Вы не можете сделать НИЧЕГО с данными ЗА ИСКЛЮЧЕНИЕМ взять его адрес, и для этого есть встроенный в язык очень хороший и безопасный метод, т. Е.

void* data;

Это имеет больше смысла?

6 голосов
/ 19 января 2009

Ссылка - это ссылка на экземпляр чего-либо. Экземпляр чего-либо не может иметь тип void. Любой экземпляр чего-либо должен иметь определенный тип (и, возможно, базовые типы).

3 голосов
/ 19 января 2009

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

Две основные причины, по которым ссылка на пустоту запрещена


1 Они были бы совершенно бесполезны.

Действительно, если мы оглянемся на времена C, указатели void имели две цели:

  • Управление памятью (например, malloc)
  • Универсальность (написание функций, которые могут принимать аргументы любого типа)

Когда вышел C ++, шаблоны стали лучшим решением для реализации универсальности. Однако настраиваемое управление памятью все еще должно было быть возможным, и взаимодействие между C ++ и C было серьезной проблемой, поэтому void * был сохранен. Гипотетическая ссылка на void не поможет в управлении памятью, а обобщенность уже описана, поэтому в основном она почти бесполезна (за исключением гарантии ненулевого значения, описанной ниже).

2 Вы не сможете ничего с этим поделать

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

void *data = // something
// using *data and data-> is forbidden

void &data = // something
// using data is forbidden

Тем не менее, мы могли бы подумать о случае использования, в котором ссылка не должна была бы быть «разыменована» (эта фраза ужасно неправильна, но вы понимаете мою точку зрения), но где мы бы взяли только ее адрес. Предположим, у меня есть следующая функция:

void foo(void *dataptr)
{
    assert(dataptr != NULL); // or != 0
    // do something with dataptr
}

Чтобы избежать этого надоедливого утверждения, я мог бы написать функцию следующим образом:

void foo(void &dataref)
{
    void *data = &dataref;
    // do something with data
}

Однако, чтобы это работало, &dataref должно быть эквивалентно dataptr, , что не так : &dataref эквивалентно &*dataptr!

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

2 голосов
/ 19 января 2009

Технически говоря, все, что гарантировано, это то, что ссылка на объект является псевдонимом для него. То, что передача указателей под капотом делается с помощью указателей, является деталью реализации. Это может сбивать с толку из-за того, что ссылки повторно используют оператор &, который также является адресом, но имейте в виду, что оператор на самом деле имеет разные значения в разных контекстах (в объявлении переменной или параметра он обозначает ссылочный тип, в противном случае это адрес адреса , за исключением случаев, когда это поразрядно - и). Поскольку технически это просто псевдоним объекта, ссылка всегда разыменовывается, как объяснил Ворриер.

1 голос
/ 20 апреля 2009

ОК, меня об этом беспокоит одна вещь. Идея void*, как упоминалось выше, заключается в том, что у вас все еще есть допустимая переменная, содержащая адрес, но тип игнорируется. Это кажется допустимым, поскольку мы все еще можем работать с адресными данными - тип в этом контексте несколько избыточен (или менее важен). Разыменование это плохо, потому что пытаться и получить доступ к члену не имеет смысла, например. p.mem. Мы не знаем, к какому классу обращаться, и, следовательно, к какой памяти нужно переходить, к указателям vtable, которым нужно следовать.

Однако тогда, похоже, имеет смысл, что p сам по себе будет в порядке, поскольку он будет ссылаться только на объект, но не на его данные. Для этого не требуется информация о классе, только адрес. Я понимаю, что это абсолютно бесполезно, но важно определить, когда что-то сломалось. Принимая это понятие, ссылка на C ++ (постоянно разыменовывается, но не имеет доступа к чему-либо), например, void& ref = static_cast< &void >(obj) также имеет смысл и, следовательно, допускает пустые ссылки. Я не говорю, что кто-то должен обсуждать это с ответственными лицами, но с "осмысленной" точки зрения это кажется правильным, нет?

Как указывал Люк Торайль выше (по крайней мере, такова моя интерпретация), его можно было бы реализовать, но проблема носит смысловой характер. Разумное объяснение, к которому я мог прийти, заключалось в том, что, поскольку переменная объекта является «тегом» для последовательности памяти, тип имеет важное семантическое значение. Таким образом, указатель, рассматриваемый как переменная со значением адреса, рассматривает тип как несколько лишний, а не ключ к его определению.

Кто-нибудь согласится с этим?

0 голосов
/ 20 апреля 2009

Ниже не защита понятия недействительных ссылок. Я предлагаю это как анекдот из дикой природы. Спросите себя, не пахнет ли это смешно.

Моя компания была одной из первых, кто использовал C ++ на коммерческой основе, и изначально компилировал с использованием Cfront . Ранние разработчики все еще изучали язык и обычно использовали все приемы в книге (операторы везде!). Вот трюк, который они считали классным:

void Foo::something(int action, ostream &os = *(ostream *)0)
{
   ostream *os_p = &os;
   if (&os == (ostream *)0) {
      os_p = &cerr;
   }
   // continue with method
}

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

0 голосов
/ 19 января 2009

Если бы они были, они бы семантически не дифференцировались от указателей и составляли бы синтаксический сахар. Ссылка гласит: «Я имею в виду то, что относится к этому типу». Разрешение void или null reference ослабит это отличие от указателей.

Конечно, ссылка может ссылаться на объект, который больше не существует, но это исключение.

0 голосов
/ 19 января 2009

Вы можете рассматривать ссылку как указатель без ссылки. Синтаксически вы обрабатываете ссылку как указатель: вам не нужен оператор * для разыменования, и вы можете использовать его. а не -> для доступа к своим членам.

Однако вы не можете разыменовать указатель void. Как указывает Binary Worrier, попытка сделать это приведет к ошибке компиляции. И если у вас не может быть разыменованного пустого указателя, это означает, что у вас не может быть пустой ссылки.

0 голосов
/ 19 января 2009

void - это то, чего по определению не существует, поэтому нелогично иметь его адрес.

...