Семантика передачи по ссылке по умолчанию в C++
-
02-07-2019 - |
Вопрос
РЕДАКТИРОВАТЬ:Этот вопрос больше касается языковой инженерии, чем самого C++.Я использовал C++ в качестве примера, чтобы показать, чего я хочу, главным образом потому, что использую его ежедневно.Я не хотел знать, как это работает на C++, но открыл дискуссию о том, как это работает. мог быть сделано.
Сейчас это работает не так, я так делаю желание это можно сделать, и это наверняка нарушит совместимость C, но я думаю, что в этом и заключается суть extern "C".
Я имею в виду, что в каждой функции или методе, который вы объявляете прямо сейчас, вы должны явно написать, что объект будет отправлен по ссылке, предварительно добавив к нему оператор ссылки.Я бы хотел, чтобы каждый тип, не относящийся к POD, автоматически отправлялся по ссылке, потому что я часто использую это, фактически для каждого объекта размером более 32 бит, а это почти каждый мой класс.
Давайте на примере, как обстоят дела сейчас, предположим а, б и с быть классами:
class example { public: int just_use_a(const a &object); int use_and_mess_with_b(b &object); void do_nothing_on_c(c object); };
Теперь чего я желаю:
class example { public: int just_use_a(const a object); int use_and_mess_with_b(b object); extern "C" void do_nothing_on_c(c object); };
Теперь do_nothing_on_c() может вести себя так же, как сейчас.
Это было бы интересно, по крайней мере, для меня, кажется гораздо более понятным, а также, если бы вы знать каждый параметр, не относящийся к POD, передается по ссылке. Я считаю, что ошибки были бы такими же, как если бы вам пришлось явно объявить его.
Другая точка зрения на это изменение, исходящая от кого-то из C, оператор ссылки кажется мне способом получить переменную адрес, именно так я получал указатели.Я имею в виду, что это один и тот же оператор, но с разной семантикой в разных контекстах, не кажется ли вам это тоже немного неправильным?
Решение
Я думаю, вы упускаете суть C++ и семантики C++.Вы упустили тот факт C++ корректно передает (почти) все по значению, потому что в C это делается так же.Всегда.Но не только в C, как я покажу вам ниже...
Семантика параметров на C
В C все передается по значению.«Примитивы» и «POD» передаются путем копирования их значений.Измените их в своей функции, и оригинал не будет изменен.Тем не менее, стоимость копирования некоторых POD может быть нетривиальной.
Когда вы используете обозначение указателя (*), вы не передаете данные по ссылке.Вы передаете копию адреса.Это более или менее то же самое, но с одним тонким отличием:
typedef struct { int value ; } P ;
/* p is a pointer to P */
void doSomethingElse(P * p)
{
p->value = 32 ;
p = malloc(sizeof(P)) ; /* Don't bother with the leak */
p->value = 45 ;
}
void doSomething()
{
P * p = malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
/* Value of p ? 25 ? 32 ? 42 ? */
}
Окончательное значение p->value равно 32.Потому что p был передан путем копирования значения адреса.Таким образом, исходный p не был изменен (и появился новый).
Семантика параметров в Java и C Sharp
Кого-то может удивить, но в Java тоже все копируется по значению.Приведенный выше пример C даст точно такие же результаты в Java.Это почти то, что вам нужно, но вы не сможете передать примитив «по ссылке/указателю» так же легко, как в C.
В C# они добавили ключевое слово «ref».Он работает более или менее похоже на ссылку в C++.Дело в том, что в C# вы должны упоминать это как в объявлении функции, так и при каждом вызове.Думаю, это не то, чего ты хочешь, опять же.
Семантика параметров в C++
В C++ почти все передается путем копирования значения.Когда вы не используете ничего, кроме типа символа, вы копируете символ (как это делается в C).Вот почему, когда вы используете *, вы передаете копию адреса символа.
Но когда вы используете &, предположим, что вы передаете реальный объект (будь то структура, int, указатель и т. д.):Ссылка.
Его легко принять за синтаксический сахар (т. е. за кулисами он работает как указатель, а сгенерированный код аналогичен тому, который используется для указателя).Но...
Правда в том, что ссылка — это больше, чем просто синтаксический сахар.
- В отличие от указателей, он позволяет манипулировать объектом, как если бы он находился в стеке.
- Нестроечные указатели, связанные с ключевым словом const, разрешают неявное преобразование из одного типа в другой (в основном через конструкторы).
- В отличие от указателей, символ не должен быть NULL/недействительным.
- В отличие от «копирования», вы не тратите бесполезное время на копирование объекта.
- В отличие от «по копированию», вы можете использовать его как параметр [out]
- В отличие от «копирования», вы можете использовать весь спектр ООП в C++ (т.е.вы передаете полный объект функции, ожидающей интерфейса).
Итак, ссылки сочетают в себе лучшее из обоих миров.
Давайте посмотрим на пример C, но с вариацией C++ функции doSomethingElse:
struct P { int value ; } ;
// p is a reference to a pointer to P
void doSomethingElse(P * & p)
{
p->value = 32 ;
p = (P *) malloc(sizeof(P)) ; // Don't bother with the leak
p->value = 45 ;
}
void doSomething()
{
P * p = (P *) malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
// Value of p ? 25 ? 32 ? 42 ?
}
Результат — 42, и старый p был заменен новым p.Потому что, в отличие от кода C, мы передаем не копию указателя, а ссылку на указатель, то есть сам указатель.
При работе с C++ приведенный выше пример должен быть предельно ясен.Если это не так, то вы что-то упускаете.
Заключение
C++ — это передача по копированию/значению, потому что именно так все работает, будь то C, C# или Java (даже в JavaScript...:-п ...).Как и в C#, в C++ есть ссылочный оператор/ключевое слово, в качестве бонуса.
Насколько я понимаю, вы, возможно, делаете то, что я называю полушутя С+, то есть C с некоторыми ограниченными возможностями C++.
Возможно, ваше решение использует определения типов (хотя ваши коллеги по C++ будут в ярости, увидев код, загрязненный бесполезными определениями типов...), но это только запутает тот факт, что вам действительно не хватает C++.
Как сказано в другом посте, вам следует изменить свое мышление с разработки на C (чего бы то ни было) на разработку на C++ или, возможно, вам следует перейти на другой язык.Но не продолжайте программировать на языке C с функциями C++, потому что, сознательно игнорируя/запутывая мощь используемых вами идиом, вы будете создавать неоптимальный код.
Примечание:И не пропускайте мимо копирования ничего, кроме примитивов.Вы кастрируете свою функцию из-за ее объектно-ориентированного потенциала, а в C++ это не то, что вам нужно.
Редактировать
Вопрос был в некотором роде изменено (см. https://stackoverflow.com/revisions/146271/list ).Я оставлю свой первоначальный ответ и отвечу на новые вопросы ниже.
Что вы думаете о семантике передачи по ссылке по умолчанию в C++? Как вы сказали, это нарушит совместимость, и у вас будет другой проход для примитивов (т.встроенные типы, которые по-прежнему будут передаваться путем копирования) и структуры/объекты (которые будут передаваться как ссылки).Вам придется добавить еще один оператор, обозначающий «передачу по значению» (внешний «C» довольно ужасен и уже используется для чего-то совершенно другого).Нет, мне очень нравится, как это делается сегодня в C++.
[...] оператор ссылки кажется мне способом получить адрес переменной, именно такой способ я использовал для получения указателей.Я имею в виду, что это один и тот же оператор, но с разной семантикой в разных контекстах, не кажется ли вам это тоже немного неправильным? Да и нет.Оператор >> также изменил свою семантику при использовании с потоками C++.Затем вы можете использовать оператор += для замены strcat.Я предполагаю, что оператор & использовался потому, что его значение «противоположно указателю», и потому что они не хотели использовать еще один символ (ASCII ограничен, а оператор области действия::а также указатель -> показывает, что можно использовать лишь несколько других символов).Но теперь, если & вас беспокоит, && действительно вас расстроит, поскольку в C++0x добавлен унарный && (своего рода суперссылка...).Я еще сам это не переварил...
Другие советы
Опция компилятора, которая полностью меняет смысл раздела кода, кажется мне очень плохой идеей.Либо используйте синтаксис C++, либо найдите другой язык.
Я бы предпочел больше не злоупотреблять ссылками, делая каждый (неполный) параметр ссылкой.
Основная причина, по которой ссылки были добавлены в C++, заключалась в том, что для поддержки перегрузки операторов;если вам нужна семантика «передачи по ссылке», у C есть вполне разумный способ сделать это:указатели.
Использование указателей ясно дает понять ваше намерение изменить значение указанного объекта, и это можно увидеть, просто взглянув на вызов функции; вам не нужно смотреть на объявление функции, чтобы увидеть, использует ли она ссылку.
Также см
Я хочу изменить аргумент, должен ли я использовать указатель или использовать ссылку? Я не знаю сильной логической причины.При передаче `` не объект '' (например,Нулевый указатель) приемлем, используя указатель имеет смысл.Мой личный стиль состоит в том, чтобы использовать указатель, когда я хочу изменить объект, потому что в некоторых контекстах это облегчает то, что модификация возможно.
Да, я считаю, что это довольно запутанная перегрузка.
Вот что Microsoft говорит о ситуации:
Не путайте ссылочные объявления с использованием оператора адреса.Если перед идентификатором & стоит тип, например int или char, идентификатор объявляется как ссылка на тип.Если перед идентификатором & не указан тип, используется оператор адреса.
Я не очень хорошо разбираюсь в C или C++, но у меня больше головной боли при выборе различных вариантов использования * и & на обоих языках, чем при написании кода на ассемблере.
Лучший совет — выработать привычку думать о том, чего вы действительно хотите.Передача по ссылке удобна, когда у вас нет конструктора копирования (или вы не хотите его использовать), и дешевле для больших объектов.Однако тогда изменения параметра ощущаются за пределами класса.Вместо этого вы могли бы пройти мимо const
ссылка - тогда мутаций нет, но вы не можете вносить локальные изменения.Передавайте константное по значению для дешевых объектов, которые должны быть доступны только для чтения в функции, и передайте неконстантное по значению, если вам нужна копия, в которую можно внести локальные изменения.
Каждая перестановка (по значению/по ссылке и константная/неконстантная) имеет важные различия, которые определенно не эквивалентны.
Когда вы передаете по значению, вы копируете данные в стек.Если у вас есть оператор =, определенный для структуры или класса, который вы ему передаете, он выполняется.Насколько я знаю, не существует директивы компилятора, которая бы смыла всю ерунду неявной языковой путаницы, которую по сути может вызвать предлагаемое изменение.
Наиболее распространенной практикой является передача значений по константной ссылке, а не просто по ссылке.Это гарантирует, что значение не может быть изменено в вызывающей функции.Это один из элементов константно-корректной кодовой базы.
Кодовая база, полностью корректная для const, идет еще дальше, добавляя const в конец прототипов.Учитывать:
void Foo::PrintStats( void ) const {
/* Cannot modify Foo member variables */
}
void Foo::ChangeStats( void ) {
/* Can modify foo member variables */
}
Если вам нужно передать объект Foo в функцию с префиксом const, вы сможете вызвать PrintStats().Компилятор выдаст ошибку при вызове ChangeStats().
void ManipulateFoo( const Foo &foo )
{
foo.PrintStats(); // Works
foo.ChangeStats(); // Oops; compile error
}
Честно говоря, я считаю, что вся эта идея передачи по значению/передаче по ссылке в C++ вводит в заблуждение. Все передается по значению.У вас есть три случая:
Где вы передаете локальную копию переменной
void myFunct(int cantChangeMyValue)
Где вы передаете локальную копию указателя на переменную
void myFunct(int* cantChangeMyAddress) { *cantChangeMyAddress = 10; }
Когда вы передаете «ссылку», но с помощью магии компилятора это все равно, как если бы вы передали указатель и просто каждый раз разыменовывали его.
void myFunct(int & hereBeMagic) { hereBeMagic = 10; // same as 2, without the dereference }
Лично мне гораздо легче помнить, что все передается по значению.В некоторых случаях это значение может быть адресом, который позволяет вам что-то менять вне функции.
То, что вы предлагаете, не позволит программисту сделать номер 1.Я лично считаю, что было бы плохой идеей отказаться от этой опции.Одним из основных преимуществ C/C++ является возможность детального управления памятью.Передача всего по ссылке — это просто попытка сделать C++ более похожим на Java.
есть что-то непонятное.Когда ты говоришь:
int b(b ¶m);
что вы имели в виду для второго «б»?ты забыл указать тип?ты забыл написать по-другому относительно первой "б"?Вам не кажется, что логично написать что-то вроде:
class B{/*something...*/};
int b(B& param);
Поскольку теперь я предполагаю, что вы имеете в виду то, что я пишу.
Теперь ваш вопрос: «Не думаете ли вы, что будет лучше, если компилятор будет рассматривать каждое передаваемое значение не-POD как передаваемое по ссылке?».Первая проблема в том, что это нарушит ваш контракт.Я полагаю, вы имеете в виду передачу по CONST-ссылке, а не только по ссылке.
Ваш вопрос теперь сводится к следующему:«Знаете ли вы, есть ли какая-нибудь директива компилятора, которая может оптимизировать вызов функции по значению?»
Теперь ответ: «Я не знаю».
Я думаю, что C++ станет очень запутанным, если вы начнете смешивать все доступные параметры с их константными вариациями.
Отслеживание всех вызовов конструкторов копирования, всех перегруженных разыменований и т. д. быстро выходит из-под контроля.