Семантика ссылок по умолчанию в C++ [закрыто]

EDIT: этот вопрос больше касается языковой инженерии, чем самого C++. Я использовал C++ в качестве примера, чтобы показать, что я хотел, в основном потому, что я использую его ежедневно. Я не хотел знать, как это работает на C++, но открыть дискуссию о том, как это может быть сделано.

это не так, как это работает прямо сейчас, вот так я желаю Это можно сделать, и это наверняка нарушит совместимость C, но это то, что я думаю, что extern " C " - это все о.

Я имею в виду, что в каждой функции или методе, которые вы объявляете прямо сейчас, вы должны явно написать, что объект будет отправлен путем префикса ссылки на него. Я хочу, чтобы каждый не-POD тип автоматически отправлялся по ссылке, потому что я использую это много, на самом деле для каждого объекта, размер которого превышает 32 бита, и это почти каждый мой класс.

давайте проиллюстрируем, как это прямо сейчас, предположим a, b и c для занятия:

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, ссылочный оператор кажется мне способом получить переменную адрес, так я использовал для получения указателей. Я имею в виду, что это один и тот же оператор, но с разной семантикой в разных контекстах, разве это не кажется вам немного неправильным?

9 ответов


Я думаю, вам не хватает точки C++ и семантики C++. Вы упустили этот факт C++ корректен в передаче (почти) всего по значению, потому что это так, как это делается в C. всегда. Но не только в C, как я покажу ниже...

семантика параметров на C

В C, все передается по значению. "примитивы" и "стручки" передаются путем копирования их значения. Измените их в своей функции, и оригинал не будет изменен. Тем не менее, стоимость копирование некоторых стручков может быть нетривиальным.

когда вы используете обозначение указателя ( * ), вы не проходите по ссылке. Вы передаете копию адреса. Что более или менее одно и то же, но с одним тонким различием:

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 - > значение равно 32. Потому что p был передан путем копирования значения адреса. Таким образом, исходный p не был изменен (и новый был просочился).

семантика параметров на Java и C Sharp

Это может быть удивительно для некоторых, но в Java, все копируется по значению, тоже. Приведенный выше пример C даст точно такие же результаты в Java. Это почти то, что вы хотите, но вы не сможете пройти примитивный "по ссылке/указателю" так же легко, как в с.

в C# они добавили ключевое слово "ref". Он работает более или менее как ссылка в C++. Дело в том, что на C# вы должны упомянуть об этом как в объявлении функции, так и при каждом вызове. Я думаю, это не то, что вы хотите, опять же.

семантика параметров на C++

В C++ почти все передается путем копирования значения. Когда вы используете только тип символа, вы копируете символ (как это делается в C). Вот почему, когда вы используете*, вы передаете копию адреса символа.

но когда вы используете&, предположим, что вы передаете реальный объект (будь то struct, int, pointer, whatever): ссылка.

Это легко принять его за синтаксический сахар (т. е. за кулисами он работает как указатель, а сгенерированный код используется для указателя). Но...

правда в том, что ссылка больше, чем синтаксический сахар.

  • в отличие от указателей, он разрешает манипулировать объектом, как будто на стеке.
  • Unline указатели, когда associatied с ключевого слова const, она разрешает скрытую рекламу из одного типа в другой (через конструкторы, в основном.)
  • в отличие от указателей, символ не должен быть NULL / invalid.
  • в отличие от" by-copy", вы не тратите бесполезное время на копирование объекта
  • в отличие от" by-copy", вы можете использовать его в качестве параметра [out]
  • в отличие от" by-copy", вы можете использовать полный диапазон ООП в 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++ - это pass-by-copy / value, потому что так все работает, будь то на C, на C# или на Java (даже в JavaScript... :- p ...). И, как и C#, C++ имеет ссылочный оператор / ключевое слово,в качестве бонуса.

сейчас, насколько я понимаю, вы, возможно, делаете то, что я называю половина-jockingly C+, то есть, C с общества с++.

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

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

Примечание: и не проходите мимо копирования ничего, кроме примитивов. Вы кастрируете свою функцию из ее емкости OO, и в C++ это не то, что вы хотите.

редактировать

вопрос несколько изменено (см. https://stackoverflow.com/revisions/146271/list). Я даю свой первоначальный ответ и отвечаю на новые вопросы ниже.

что вы думаете о default семантика pass-by-reference на C++? как вы сказали, это нарушит совместимость, и у вас будет другой проход для примитивов (т. е. встроенных типов, которые все равно будут передаваться копией) и структур/объектов (которые будут передаваться как ссылки). Вам нужно будет добавить другой оператор, чтобы означать " pass-by-value "(extern" C " довольно ужасен и уже используется для чего-то другого). Нет, мне очень нравится, как это делается сегодня на C++.

[...] этот оператор reference кажется мне способом получить адрес переменной, именно так я использовал для получения указателей. Я имею в виду, что это один и тот же оператор, но с разной семантикой в разных контекстах, разве это не кажется вам немного неправильным? да и нет. Оператор > > изменил свою семантику при использовании с потоками c++. Затем вы можете использовать operator += для замены strcat. Я думаю, оператор & привык, потому что его значение как "противоположность указателя" , и потому что они не хотели использовать еще один символ (ASCII ограничен, а оператор scope::, а также указатель - > показывает, что можно использовать несколько других символов). Но теперь, если & беспокоит вас, & & действительно расстроит вас, поскольку они добавили унарный && в C++0x (своего рода супер-ссылку...). Я сам еще не переварил...


опция компилятора, которая полностью меняет значение раздела кода, звучит для меня как очень плохая идея. Либо используйте синтаксис C++, либо найдите другой язык.


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

основной причиной добавления ссылок на C++ было для поддержки перегрузки операторов; если вы хотите семантику "pass-by-reference", у C был вполне разумный способ сделать это: указатели.

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

Также см.

я хочу изменить аргумент, должен ли я использовать указатель или должен использовать ссылку? Я не знаю сильный логическая причина. Если проходящий `не объект " (например, нулевой указатель) приемлемо, используя указатель делает чувство. Мой личный стиль-использовать указатель, когда я хочу изменить объект, потому что в некоторых контекстах делает легче заметить, что модификация возможна.

из того же FAQ.


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

Это то, что microsoft должна сказать о ситуации:

Не путайте ссылочные объявления с использованием адреса оператора. Когда & identifier предшествует типу, например int или char, идентификатор объявляется как ссылка на тип. Если & identifier не предшествует типу, используется оператор address-of.

Я не действительно здорово на C или C++, но у меня больше головных болей, сортируя различные виды использования * и & на обоих языках, чем я кодирую в ассемблере.


лучший совет-сделать привычку думать о том, что вы действительно хотите, чтобы произошло. Передача по ссылке хороша, когда у вас нет конструктора копирования (или не хотите его использовать), и это дешевле для больших объектов. Однако затем мутации параметра ощущаются вне класса. Вы могли бы вместо этого пройти мимо const ссылка -- тогда нет мутаций, но вы не можете вносить локальные изменения. Pass const by-value для дешевых объектов, которые должны быть доступны только для чтения в функции и передайте не-const по-значению, если вам нужна копия, в которую вы можете внести локальные изменения.

каждая перестановка (по значению/по ссылке и const/non-const) имеет важные различия, которые определенно не эквивалентны.


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

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

полностью 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++ вводит в заблуждение. все проходит по значению. У вас три случая:--6-->

  1. где вы передаете локальную копию переменной

    void myFunct(int cantChangeMyValue)
    
  2. где вы передаете локальную копию указателя на переменную

    void myFunct(int* cantChangeMyAddress) {
        *cantChangeMyAddress = 10;
    }
    
  3. где вы передаете "ссылку", но через магию компилятора это так же, как если бы вы передали указатель и просто разыграли его каждый раз.

    void myFunct(int & hereBeMagic) {
        hereBeMagic = 10; // same as 2, without the dereference
    }
    

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

что вы предлагаете, не позволит программисту сделать номер 1. Я лично думаю, что было бы плохой идеей отказаться от этого варианта. Один из главных плюсов C / C++ имеет мелкозернистую память управление. Заставить все пройти по ссылке просто пытается сделать C++ более похожим на Java.


есть что-то не понятно. когда вы говорите:

int b (b &param);

что вы намеревались сделать для второй "Б"? вы забыли ввести тип? вы забыли написать иначе по отношению к первой букве "б"? вы не думаете, что это ясно, чтобы написать что-то вроде:

class B{/*something...*/};
int b(B& param);

С этого момента, я полагаю, вы имеете в виду то, что я пишу.

теперь ваш вопрос: "не думаете ли вы, что будет лучше, если компилятор рассмотрим каждое значение пропуска не-POD как pass-by-ref?". Первая проблема в том, что это разорвет ваш контракт. Я предполагаю, что вы имеете в виду проходную ссылку, а не только ссылку.

теперь ваш вопрос сводится к следующему: "знаете ли вы, есть ли директива компиляторов, которая может оптимизировать вызов функции по значению?"

ответ теперь "я не знаю".


Я думаю, что c++ станет очень грязным, если вы начнете смешивать все доступные параметры с их вариациями const.

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