Разница заключается в том, к чему относится квалификатор &
: тип или переменная?
Допустим, у вас есть тип 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++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 =
.