Передача объявлений по ссылкам в C ++ - PullRequest
1 голос
/ 21 августа 2011

Я хочу установить, верно ли мое понимание некоторых базовых справочных принципов C ++.Насколько я понимаю, заявляю:

void foo(string &arg); //Means get the memory reference of passed argument
{
  cout << arg;        
}  

string arg;
string& arg1; //Means pass the memory reference of arg

Я прав?

РЕДАКТИРОВАНИЕ

Ответы [ 3 ]

13 голосов
/ 21 августа 2011

Разница заключается в том, к чему относится квалификатор &: тип или переменная?

Допустим, у вас есть тип T.

Для объявлений / параметров (& - это спецификатор типа):

T v1 = 13 ;    // v1 is a variable of type T, whose value is 13
T v2 = 42 ;    // v2 is another variable of type T, whose value is 42
T * v3 ;       // v3 is an uninitialized pointer to a variable of type T
T * v4 = &v1;  // v4 is pointer pointing to v1 (the value of v4 is the
               // address of v1)
T & v5 = v1 ;  // v5 is an alias of v1
T & v6 ;       // WON'T COMPILE. An alias MUST be initialized.

Для операций (& является оператором):

&v1 ;    // returns the address of v1 (e.g. 0x00ABCDEF)
v4 ;     // returns 0x00ABCDEF (because v4 was initialized to point to
         // v1)
*v4 ;    // returns the dereferenced value of pointer v4, that is: 13
v5 ;     // returns the value inside v1 (the aliased variable of v5),
         // that is: 13

Теперь мы можем смешать две записи:

// We can reattribute the variables pointed by pointers
T * v7 = &v1 ; // v7 is a pointer to the variable v1
*v7 ;          // returns v1's value, that is 13
v7 = &v2 ;     // v7 is now pointing to v2
*v7 ;          // returns v2's value, that is 42

// We cannot reattribute the aliases referencing variables
// because once initialized, aliases **are** the variable they
// were initialized to.
v5 = v2 ;      // v5 is an alias of v1, so this won't reattribute it
               // instead, it will put the value of v2 into v5 and
               // thus v1
               // this, writing v5 = v2 is like writing v1 = v2
v2 ;           // v2 is still 42
v1 ;           // v1 has a value of 42 (since the v5 = v2 line above)
v5 ;           // v5 is still the alias of v1, and thus, its value is 42
v2 = 57 ;      // v2's value is now 57
v1 ;           // v1's value is still 42 (changing v2 has no impact on
               // v1 because they are NOT aliases. They are distinct
               // variables
v5 ;           // v5 is still the alias of v1, and thus, its value is
               // still 42

Деталь: О C

Язык C имел только понятие значения и указатель на значение (и указатель на указатель на значение, и указатель на ... и т. Д.), Означая, что у вас было понятие ссылки / разыменования (не связанного со ссылками на C ++ ...) с унарными операторами & и *.

T ** p ;     // is the declaration of a pointer to a pointer
             // to a value of type T

p ;          // is still the pointer to pointer
&p ;         // returns the address of the p variable
             // meaning you can put that address in a variable
             // of type T ***:
T *** pp = &p ;

&&p ;        // This has no meaning whatsoever in C and C++
             // because an address is a simple raw number, and a
             // a number has no address: Only variables have
             // addresses

*p ;         // this is p, dereferenced once, meaning you get
             // to have the value at the address given by p, which
             // is still an address (a pointer of type T *)
**p ;        // this is p, dereferenced twice, meaning you get
             // to have the value at the address given by *p,
             // which is of type T

Проблема в том, что унарные операторы & и * на самом деле не симметричны. Например:

T t = v ;
T * p = &t ;
T * p2 = &t ;       // p and p2 are two different pointers containing
                    // the same address, and thus pointing at the same
                    // value v
p == p2 ;           // is true, because both different pointers contain
                    // the same address
*p == *p2 ;         // is true, because both different pointers point
                    // to the same value (as they contain the same
                    // address)
&p == &p2  ;        // is false, because each variable p and p2 is
                    // distinct from the other, and thus, have different
                    // addresses

Итак, в C:

  • унарный оператор * извлечет значение по адресу, содержащемуся в переменной указателя
  • унарный оператор & будет извлекать адрес переменной

Подробно: о C ++

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

В C ++, помимо применения квалификатора & к переменной (которая получает ее адрес), вы можете применить ее вместо типа (который делает ее переменную ссылкой / псевдонимом) .

Итак, когда у вас есть:

T t = v ;
T * p = &t ;    // p is a pointer containing the address of the t
                // variable
T ** pp = &p ;  // pp is a pointer containing the address of the p
                // variable
T & r = t ;     // r is a reference to/an alias of t. It behaves as
                // if it was t in all aspects
T *& r = p ;    // If you understand that line, then you're ready for
                // C++ references (i.e. r is an alias of a pointer to T,
                // here, an alias of p)
T **& rr = pp ; // rr is an alias of a pointer to a pointer to T,
                // here, an alias of pp)

Я предполагаю здесь, но вполне вероятно, что ссылки r и rr оптимизированы во время компиляции (то есть только t и p остаются)

Подробно: о C ++ 11 (, ранее известный как C ++ 0x )

Поскольку этот вопрос был помечен C++0x, я расскажу об этом и о новой ссылке && r-value.

Ссылки / псевдонимы не изменились с C ++ на C ++ 11. Но есть еще один тип «ссылки», который был введен (как спецификатор типа &&), то есть ссылка на r-значение, в дополнение к простым ссылкам / псевдонимам C ++.

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

Семантика перемещения была добавлена ​​для решения этой проблемы: зачем создавать много копий одного и того же объекта, если в конце мы будем сбрасывать копии в мусор и сохранять только последний?

Например, следующий код:

  1  |  T foo()
  2  |  {
  3  |     T a ;
  4  |     // put some values in T
  5  |     return a ;
  6  |  }
  7  |  
  8  |  void bar()
  9  |  {
 10  |     T b = foo() ;
 11  |  }

Запрет оптимизации ( возвращаемое значение-оптимизация приходит на ум, но также вставка ), этот код создаст значение a (строка 3) или тип T , Когда он возвращает тип T (строка 5), он создаст временную копию a, которую мы назовем x, а затем уничтожим a.

В строке 10 значение b будет инициализировано с использованием временного значения x (которое является так называемым значением r), ​​а затем x будет уничтожено.

Это означает, что для инициализации b вы создали две переменные, одну явно (a) и одну косвенно x), которые вскоре были уничтожены, что может быть дорогостоящим, если конструкция типа T дорогой.

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

Решение состоит в том, чтобы создать конструктор перемещения (и, возможно, ход operator =, для полноты), то есть что-то со следующим прототипом:

T::T(T && p_t) ;                 // move constructor
T & T::operator = (T && p_t) ;   // move operator =

Что можно сравнить с обычными конструкторами копирования C ++ / operator =:

T::T(const T & p_t) ;                 // copy constructor
T & T::operator = (const T & p_t) ;   // operator =

Итак, вернемся к примеру выше, мы добавим семантику перемещения в T:

class T
{
   V * m_v ; // some complex data, expensive to create
             // and expensive to destroy
   // etc.
}

// destructor :
// Clean its internals if needed
T::~T()
{
   delete this->m_v ; // potentially expensive if m_v is not NULL
}

// copy constructor :
// Do not modify the original, and make a copy of its internals
T::T(const T & p_t)
{
   this->m_v = new V(p_t.m_v) ; // potentially expensive
}

// move constructor
// the original is a temporary (guaranteed by the compiler)
// so you can steal its internals, as long as you keep the
// temporary clean, no one cares
T::T(T && t)
{
   this->m_v = t.m_v ; // stealing the internals of the temporary
   t.m_v = NULL ;      // the temporary is now "empty"
}

Таким образом, приведенный выше код (с foo и bar, без каких-либо изменений) позволит избежать создания двух временных объектов типа T из-за поддержки семантики перемещения T.

P.S .: добавление конструктора перемещения означает, что вы также должны добавить движение operator =.

7 голосов
/ 21 августа 2011
string str;
string &arg1 = str; 
string& arg2 = str; 
string *ptr = &str;

Значения arg1 & arg2 являются ссылками на переменную str типа string, это означает, что они являются просто псевдонимом для переменной str.
Они обапо сути, объявляем ссылочную переменную, как сказано выше, это просто вопрос стиля, в котором помещается &.

ptr - указатель на переменную str, которая имеет тип string.

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

string& arg2;

Компилятор выдаст вам за это ошибку, например:

error: 'arg2' объявлен как ссылка, но неинициализированный

0 голосов
/ 21 августа 2011

Ни один из ваших примеров не выглядит законным из-за точек с запятой.Но игнорируя эти

  1. Совсем не выглядит законным
  2. Если предположить, что arg является указателем, тогда * arg означает значение объекта, на который указывает (строго это ссылка назначение, но пока игнорируем)
  3. Выглядит как объявление, и если да, то объявляет arg как ссылку на строку
  4. То же, что 3, пробел не имеет значения

На самом деле невозможно сказать значение без контекста.Так что, если это не ясно, напишите более полный код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...