Когда константная ссылка лучше, чем передача по значению в C++11?

У меня есть некоторый код до C++11, в котором я использую const ссылки для передачи больших параметров, таких как vectorмного. Пример таков:

int hd(const vector<int>& a) {
   return a[0];
}

Я слышал, что с новыми функциями C++11 вы можете передать vector по стоимости следующим образом без учета производительности.

int hd(vector<int> a) {
   return a[0];
}

Например, этот ответ говорит

Семантика перемещения в C++11 делает передачу и возврат по значению гораздо привлекательнее даже для сложных объектов.

Правда ли, что вышеупомянутые два варианта одинаковы с точки зрения производительности?

Если да, то когда использование константной ссылки, как в варианте 1, лучше, чем вариант 2? (то есть, почему мы все еще должны использовать константные ссылки в C++11).

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

5 ответов

Решение

Общее правило для передачи по значению - когда вы все равно сделаете копию. То есть, вместо того, чтобы делать это:

void f(const std::vector<int>& x) {
    std::vector<int> y(x);
    // stuff
}

где вы сначала передаете const-ref, а затем копируете его, вы должны сделать это вместо этого:

void f(std::vector<int> x) {
    // work with x instead
}

Это было частично верно в C++03 и стало более полезным с семантикой перемещения, так как копия может быть заменена перемещением в случае передачи по значению, когда функция вызывается с помощью значения r.

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

Существует большая разница. Вы получите копию vectorвнутренний массив, если он не собирался умирать.

int hd(vector<int> a) {
   //...
}
hd(func_returning_vector()); // internal array is "stolen" (move constructor is called)
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
hd(v); // internal array is copied (copy constructor is called)

C++ 11 и введение ссылок на rvalue изменили правила возврата объектов, таких как векторы, - теперь вы можете сделать это (не беспокоясь о гарантированной копии). Не изменилось никаких базовых правил принятия их как аргумента - вы все равно должны брать их по константной ссылке, если только вам не нужна настоящая копия - тогда принимайте по значению.

Семантика перемещения в C++11 делает передачу и возврат по значению гораздо привлекательнее даже для сложных объектов.

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

int hd(vector<int> a) {

Таким образом, C++11 не влияет на это.

Даже если вы правильно объявили 'hd', чтобы взять значение

int hd(vector<int>&& a) {

это может быть дешевле, чем передача по стоимости, но выполнение успешного хода (в отличие от простого std::move что может не иметь никакого эффекта) может быть дороже, чем простой переход по ссылке. Новый vector<int> должны быть построены, и он должен взять на себя ответственность за содержание a, У нас нет старых накладных расходов на необходимость выделения нового массива элементов и копирования значений, но нам все еще нужно передать поля данных vector,

Что еще более важно, в случае успешного перемещения, a будет уничтожен в этом процессе:

std::vector<int> x;
x.push(1);
int n = hd(std::move(x));
std::cout << x.size() << '\n'; // not what it used to be

Рассмотрим следующий полный пример:

struct Str {
    char* m_ptr;
    Str() : m_ptr(nullptr) {}
    Str(const char* ptr) : m_ptr(strdup(ptr)) {}
    Str(const Str& rhs) : m_ptr(strdup(rhs.m_ptr)) {}
    Str(Str&& rhs) {
      if (&rhs != this) {
        m_ptr = rhs.m_ptr;
        rhs.m_ptr = nullptr;
      }
    }
    ~Str() {
      if (m_ptr) {
        printf("dtor: freeing %p\n", m_ptr)
        free(m_ptr);
        m_ptr = nullptr;
      }
    }
};

void hd(Str&& str) {
  printf("str.m_ptr = %p\n", str.m_ptr);
}

int main() {
  Str a("hello world"); // duplicates 'hello world'.
  Str b(a); // creates another copy
  hd(std::move(b)); // transfers authority for b to function hd.
  //hd(b); // compile error
  printf("after hd, b.m_ptr = %p\n", b.m_ptr); // it's been moved.
}

Как общее правило:

  • Передать по значению для тривиальных объектов,
  • Передать по значению, если место назначения нуждается в изменяемой копии,
  • Передайте по значению, если вам всегда нужно сделать копию,
  • Передача по константной ссылке для нетривиальных объектов, когда зрителю нужно только видеть содержимое / состояние, но не нужно, чтобы его можно было изменять,
  • Переместить, когда пункт назначения нуждается в изменяемой копии временного / созданного значения (например, std::move(std::string("a") + std::string("b"))),
  • Переместите, когда вам требуется локальность состояния объекта, но вы хотите сохранить существующие значения / данные и освободить текущий держатель.

Помните, что если вы не передаете значение r, то передача по значению приведет к полной копии. Вообще говоря, передача по значению может привести к снижению производительности.

Ваш пример ошибочен. C++11 не дает вам двигаться с кодом, который у вас есть, и копия будет сделана.

Тем не менее, вы можете получить движение, объявив функцию для получения ссылки на rvalue, а затем передав ее:

int hd(vector<int>&& a) {
   return a[0];
}

// ...
std::vector<int> a = ...
int x = hd(std::move(a));

Это при условии, что вы не будете использовать переменную a снова в вашей функции, кроме как уничтожить ее или присвоить ей новое значение. Вот, std::move преобразует значение в ссылку rvalue, позволяя переместить.

Константные ссылки позволяют создавать временные записи без вывода сообщений. Вы можете передать что-то подходящее для неявного конструктора, и будет создан временный объект. Классическим примером является массив символов, преобразуемый в const std::string& но с std::vector, std::initializer_list могут быть преобразованы.

Так:

int hd(const std::vector<int>&); // Declaration of const reference function
int x = hd({1,2,3,4});

И, конечно же, вы можете перенести временный в:

int hd(std::vector<int>&&); // Declaration of rvalue reference function
int x = hd({1,2,3,4});
Другие вопросы по тегам