Когда ссылка const лучше, чем pass-by-value в C++11?


у меня есть некоторый код pre-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 делает передачу и возврат по значению намного более привлекательными даже для сложных объекты.

правда ли, что эти два варианта одинаковы по производительности?

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

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

5 54

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, и стало более полезным с семантикой перемещения, как копия мая быть заменен на перемещение в случае pass-by-val, когда функция вызывается с rvalue.

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

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

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 изменили правила о возврате объектов, таких как векторы - теперь вы можно сделать (не беспокоясь о гарантированной копии). Нет основных правил, о С они в качестве аргумента изменились, хотя - вы все равно должны взять их по ссылке const, если вам действительно не нужна реальная копия-возьмите тогда по значению.

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

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

образец, который вы даете, однако, является образцом pass by value

int hd(vector<int> a) {

так что C++11 не имеет никакого влияния на это.

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

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

это может быть дешевле, чем pass-by-value, но выполнение удачный ход (в отличие от простого std::move который может не иметь никакого эффекта вообще) может быть дороже, чем простой pass-by-reference. Новый 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.
}

как правило:

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

ваш пример ошибочен. В 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 позволяют создавать временные файлы без вывода сообщений. Вы можете передать что-то, что подходит для неявного конструктора, и будет создан временный. Классический пример-массив символов, преобразуемый в const std::string& но с std::vector, a 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});