int против const int & - PullRequest
       8

int против const int &

55 голосов
/ 16 января 2011

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

Что ты думаешь? Стоит ли писать const int & over int ? Я думаю, что он все равно оптимизирован компилятором, так что, может быть, я просто трачу свое время на его кодирование, а?

Ответы [ 6 ]

131 голосов
/ 16 января 2011

В C ++ очень часто встречается то, что я считаю анти-паттерном, использующим const T& как умный способ просто сказать T при работе с параметрами. Однако значение и ссылка (независимо от того, является ли она константной или нет) - это две совершенно разные вещи, и всегда и вслепую использование ссылок вместо значений может привести к незначительным ошибкам.

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

В качестве примера, одним из мест, где применяется этот анти-шаблон, является сама стандартная библиотека, где std::vector<T>::push_back принимает в качестве параметра a const T& вместо значения, и это может привести, например, к следующему коду:

std::vector<T> v;
...
if (v.size())
    v.push_back(v[0]); // Add first element also as last element

Этот код является бомбой замедленного действия, потому что std::vector::push_back хочет постоянную ссылку, но выполнение push_back может потребовать перераспределения, и если это произойдет, это означает, что после перераспределения полученная ссылка больше не будет действительной ( время жизни проблема), и вы входите в царство Неопределенное поведение.

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

struct P2d
{ 
    double x, y;
    P2d(double x, double y) : x(x), y(y) {}
    P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
    P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};

struct Rect
{
    P2d tl, br;
    Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
    Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
    Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};

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

Если, однако, перевести прямоугольник обратно в начало координат, вы пишете myrect -= myrect.tl;, код не будет работать, потому что был определен оператор перевода, принимающий ссылку, которая (в этом случае) ссылается на член того же экземпляра.

Это означает, что после обновления топлифта с помощью tl -= p; он будет равен (0, 0), как и должно быть, но также p станет одновременно (0, 0), потому что p - это просто ссылка на верхний левый член и поэтому обновление нижнего правого угла не будет работать, потому что оно переведет его на (0, 0), следовательно, ничего не делая.

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

В const T& слово const выражает свойство ссылки , а не ссылочного объекта : это свойство делает невозможным его использование изменить объект. Вероятно, readonly было бы лучшим именем, поскольку const имеет IMO психологический эффект от идеи, что объект будет постоянным, пока вы используете ссылку.

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

Также константная ссылка всегда будет означать проблемы для оптимизатора, так как компилятор вынужден быть параноиком, и каждый раз, когда выполняется любой неизвестный код, он должен предполагать, что все ссылочные объекты теперь могут иметь различное значение (const для ссылки для оптимизатора абсолютно НИЧЕГО, это слово предназначено только для помощи программистам - лично я не уверен, что это такая большая помощь, но это другая история).

14 голосов
/ 16 января 2011

Как говорит Оли, возвращение const T& в отличие от T - это совершенно разные вещи, которые могут сломаться в определенных ситуациях (как в его примере).

Взятие const T& в отличие от простогоT в качестве аргумента с меньшей вероятностью может что-то испортить, но все же имеет несколько важных отличий.

  • Принимая T вместо const T&, требуется, чтобы T можно было копировать.*
  • Взятие T вызовет конструктор копирования, который может быть дорогим (а также деструктор при выходе из функции).
  • Взятие T позволяет вам изменять параметр как локальную переменную (можетБыстрее, чем копирование вручную).
  • Прием const T& может быть медленнее из-за выровненных временных значений и стоимости косвенного обращения.
8 голосов
/ 16 января 2011

int & и int не являются взаимозаменяемыми! В частности, если вы возвращаете ссылку на локальную переменную стека, поведение не определено, например:

int &func()
{
    int x = 42;
    return x;
}

Вы можете вернуть ссылку на что-то, что не будет уничтожено в конце функции (например, статическое или член класса). Так что это действительно:

int &func()
{
    static int x = 42;
    return x;
}

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

Преимущество справочника состоит в том, что копия не требуется, что важно, если вы имеете дело с объектами большого класса. Однако во многих случаях компилятор может оптимизировать это; см. например http://en.wikipedia.org/wiki/Return_value_optimization.

7 голосов
/ 16 января 2011

Если вызываемый и вызывающий определены в отдельных единицах компиляции, то компилятор не может оптимизировать ссылку. Например, я скомпилировал следующий код:

#include <ctime>
#include <iostream>

int test1(int i);
int test2(const int& i);

int main() {
  int i = std::time(0);
  int j = test1(i);
  int k = test2(i);
  std::cout << j + k << std::endl;
}

с G ++ в 64-битной Linux на уровне оптимизации 3. Первый вызов не требует доступа к основной памяти:

call    time
movl    %eax, %edi     #1
movl    %eax, 12(%rsp) #2
call    _Z5test1i
leaq    12(%rsp), %rdi #3
movl    %eax, %ebx
call    _Z5test2RKi

Строка # 1 напрямую использует возвращаемое значение в eax в качестве аргумента для test1 в edi. Строки # 2 и # 3 помещают результат в основную память и помещают адрес в первый аргумент, потому что аргумент объявлен как ссылка на int, и поэтому должна быть возможность, например, взять его адрес. В наши дни может быть очень важно, можно ли что-то вычислить целиком с помощью регистров или для доступа к основной памяти. Таким образом, помимо того, что нужно больше печатать, const int& также может быть медленнее. Эмпирическое правило: передайте все данные, размер которых не превышает размер слова, по значению, а все остальное по ссылке на const. Также передайте шаблонные аргументы по ссылке на const; поскольку у компилятора есть доступ к определению шаблона, он всегда может оптимизировать ссылку.

3 голосов
/ 16 января 2011

Вместо того, чтобы «думать», что он оптимизирован компилятором, почему бы вам не получить список ассемблера и точно это выяснить?вывод ассемблера (elided):

_Z6my_intv:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    $5, %eax
    ret
    .cfi_endproc

...

_Z10my_int_refv:
.LFB1:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    $_ZZ10my_int_refvE1v, %eax
    ret

Инструкции movl в обоих очень различны.Первый перемещает 5 в EAX (который является регистром, традиционно используемым для возврата значений в коде x86 C), а второй перемещает адрес переменной (подробности исключены для ясности) в EAX.Это означает, что вызывающая функция в первом случае может просто напрямую использовать операции регистра, не нажимая на память, чтобы использовать ответ, в то время как во втором она должна ударить память через возвращенный указатель..

Это помимо других ответов, которые вы дали здесь, объясняет, почему T и const T& не являются взаимозаменяемыми.

0 голосов
/ 15 мая 2019

int это отличается с const int &:

  1. const int & является ссылкой на другую целочисленную переменную (int B), что означает: если мы изменим int B, значение const int & также изменится.

2, int является копией значения другой целочисленной переменной (int B), что означает: если мы изменим int B, значение int не изменится.

См. Следующий код C ++:

int main () {

вектор а {1,2,3};

int b = a [2]; // значение не изменяется даже при изменении вектора

const int & c = a [2]; // это ссылка, поэтому значение зависит от вектора;

а [2] = 111;

// b выведет 3;

// c выдаст 111;

}

...