Что лучше в C ++ - передавать по значению или передавать по постоянной ссылке?

StackOverflow https://stackoverflow.com/questions/270408

Вопрос

Что лучше в C ++ - передавать по значению или передавать по постоянной ссылке?

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

Это было полезно?

Решение

Обычно рекомендовано использовать 1 для передачи по const ref для всех типов , кроме встроенных ( char , int , double и т. д.), для итераторов и для объектов функций (лямбда-выражения, классы, производные от std :: * _ function ).

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

В C ++ 11 мы получили семантику перемещения . Короче говоря, семантика перемещения позволяет в некоторых случаях передавать объект & # 8220; по значению & # 8221; не копируя это. В частности, это тот случай, когда передаваемый объект является rvalue .

Само по себе перемещение объекта по крайней мере так же дорого, как и передача по ссылке. Однако во многих случаях функция все равно будет внутренне копировать объект & # 8212; то есть он будет владельцем аргумента. 2

В этих ситуациях мы имеем следующий (упрощенный) компромисс:

<Ол>
  • Мы можем передать объект по ссылке, а затем выполнить внутреннее копирование.
  • Мы можем передать объект по значению.
  • & # 8220; Передать по значению & # 8221; по-прежнему вызывает копирование объекта, если только объект не является значением. В случае значения r, объект можно вместо этого переместить, так что второй случай внезапно перестает быть копией, а затем перемещением & # 8221; но & # 8220; двигайтесь, а затем (потенциально) двигайтесь снова & # 8221;.

    Для больших объектов, в которых реализованы правильные конструкторы перемещения (например, векторы, строки & # 8230;), второй случай тогда значительно более эффективен, чем первый. Поэтому рекомендуется использовать передачу по значению, если функция получает владение аргументом и если тип объекта поддерживает эффективное перемещение .

    <Ч>

    Историческая справка:

    Фактически, любой современный компилятор должен быть в состоянии выяснить, стоит ли передавать по значению дорого, и неявно преобразовывать вызов в использование const ref, если это возможно.

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

    Но в целом компилятор не может этого определить, и появление семантики перемещения в C ++ сделало эту оптимизацию гораздо менее актуальной.

    <Ч>

    1 Например, в Скотте Мейерсе, Эффективный C ++ .

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

    Другие советы

    Редактировать: Новая статья Дейва Абрахамса о cpp-next:

    Хотите скорости?Передать по значению.


    Передача по значению для структур, где копирование обходится дешево, имеет дополнительное преимущество в том, что компилятор может предположить, что объекты не имеют псевдонимов (не являются одними и теми же объектами).Используя передачу по ссылке, компилятор не может предполагать это всегда.Простой пример:

    foo * f;
    
    void bar(foo g) {
        g.i = 10;
        f->i = 2;
        g.i += 5;
    }
    

    компилятор может оптимизировать его в

    g.i = 15;
    f->i = 2;
    

    поскольку он знает, что f и g не находятся в одном и том же местоположении.если бы g было ссылкой (foo &), компилятор не смог бы этого предположить.начиная с g.i, тогда псевдоним мог бы быть равен f-> i и должен иметь значение 7.таким образом, компилятору пришлось бы повторно извлекать новое значение g.i из памяти.

    Для получения более практических правил, вот хороший набор правил, найденный в Перемещать конструкторы статья (настоятельно рекомендуется к прочтению).

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

    "Примитивный" выше означает в основном небольшие типы данных длиной в несколько байт, которые не являются полиморфными (итераторы, функциональные объекты и т.д.) Или дорогостоящими для копирования.В этой статье есть еще одно правило.Идея заключается в том, что иногда хочется сделать копию (в случае, если аргумент не может быть изменен), а иногда и не хочется (в случае, если кто-то хочет использовать сам аргумент в функции, если аргумент в любом случае был временным, например).В документе подробно объясняется, как это можно сделать.В C ++ 1x этот метод может использоваться изначально с языковой поддержкой.До тех пор я бы придерживался вышеуказанных правил.

    Примеры:Чтобы сделать строку заглавной и вернуть версию в верхнем регистре, всегда следует передавать по значению:В любом случае, нужно сделать его копию (невозможно изменить ссылку const напрямую) - так что лучше сделайте это как можно более прозрачным для вызывающего абонента и сделайте эту копию как можно раньше, чтобы вызывающий абонент мог максимально оптимизировать - как подробно описано в этом документе:

    my::string uppercase(my::string s) { /* change s and return it */ }
    

    Однако, если вам все равно не нужно изменять параметр, используйте его по ссылке на const:

    bool all_uppercase(my::string const& s) { 
        /* check to see whether any character is uppercase */
    }
    

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

    bool try_parse(T text, my::string &out) {
        /* try to parse, write result into out */
    }
    

    Зависит от типа. Вы добавляете небольшие накладные расходы на необходимость ссылки и разыменования. Для типов с размером, равным или меньшим, чем указатели, которые используют ctor копирования по умолчанию, вероятно, будет быстрее передать по значению.

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

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

    Если вы занимаетесь шаблонным программированием, вы обычно вынуждены всегда проходить по const ref, так как не знаете, какие типы передаются. Передача штрафов за передачу чего-то плохого по значению намного хуже, чем штрафы за передачу встроенного. в типе по const ref.

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

    Это то, чем я обычно занимаюсь при разработке интерфейса не шаблонной функции:

    <Ол>
  • Передайте по значению, если функция не хочет изменять параметр и Копировать значение дешево (int, double, float, char, bool и т. д. ... Обратите внимание, что std :: string, std :: vector и остальные контейнеры в стандартной библиотеке НЕ являются)

  • Передача по константному указателю, если копировать значение дорого, а функция выполняет не хотите изменять указанное значение, а NULL - это значение, которое обрабатывает функция.

  • Передача по неконстантному указателю, если значение дорого для копирования и функция хочет изменить указанное значение, а NULL - это значение, которое обрабатывает функция.

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

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

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

    Как правило, значение для не-классовых типов и константная ссылка для классов. Если класс действительно маленький, то, вероятно, лучше передать по значению, но разница минимальна. Чего вы действительно хотите избежать, так это передавая некоторый гигантский класс по значению и дублируя его - это будет иметь огромное значение, если вы передадите, скажем, std :: vector с довольно большим количеством элементов в нем.

    Передача по значению для небольших типов.

    Передача по константным ссылкам для больших типов (определение больших может различаться для разных машин), НО в C ++ 11 передают по значению, если вы собираетесь использовать данные, поскольку вы можете использовать семантику перемещения. Например:

    class Person {
     public:
      Person(std::string name) : name_(std::move(name)) {}
     private:
      std::string name_;
    };
    

    Теперь вызывающий код будет делать:

    Person p(std::string("Albert"));
    

    И только один объект будет создан и перемещен непосредственно в член name _ в классе Person . Если вы передадите константную ссылку, необходимо будет сделать копию для помещения ее в name _ .

    Простая разница: - В функции у нас есть входной и выходной параметры, поэтому, если передаваемый входной и выходной параметры одинаковы, используйте вызов по ссылке, если входной и выходной параметры отличаются, то лучше использовать вызов по значению.

    пример недействительная сумма (int account, int deposit, int total)

    входной параметр: счет, депозит выходной параметр: всего

    вход и выход - это другой вызов использования vaule

    1. недействительная сумма (int total, int deposit)
    2. введите общий депозит итоговая сумма

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top