Когда значение константы const лучше, чем значение pass-by-value в С++ 11?
У меня есть код pre-С++ 11, в котором я использую ссылки const
для передачи больших параметров, таких как vector
. Пример следующий:
int hd(const vector<int>& a) {
return a[0];
}
Я слышал, что с новыми функциями С++ 11 вы можете передать vector
по значению следующим образом без обращений к производительности.
int hd(vector<int> a) {
return a[0];
}
Например, этот ответ говорит
С++ 11 перемещение семантики делает передачу и возврат по значению гораздо более привлекательным даже для сложных объектов.
Правда ли, что эти два параметра одинаковы по производительности?
Если да, то когда используется ссылка const, как в варианте 1, лучше, чем опция 2? (то есть зачем нам все еще нужно использовать ссылки const в С++ 11).
Одна из причин, по которой я спрашиваю, заключается в том, что ссылки на const ссылаются на вычет параметров шаблона, и было бы намного проще использовать только пропущенные значения, если это то же самое со ссылкой на const reference-wise.
Ответы
Ответ 1
Общее правило для передачи по значению - это когда вы в конечном итоге создадите копию. То есть, вместо этого:
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
}
Это частично было верно в С++ 03 и стало более полезным с семантикой перемещения, поскольку копия может быть заменена перемещением в случае pass-by-val, когда функция вызывается с rvalue.
В противном случае, когда все, что вы хотите сделать, - это прочитать данные, передача с помощью const
ссылки по-прежнему является предпочтительным, эффективным способом.
Ответ 2
Есть большая разница. Вы получите копию внутреннего массива 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)
С++ 11, а введение ссылок rvalue изменило правила возврата объектов, таких как векторы, - теперь вы можете это сделать (не беспокоясь о гарантированной копии). Однако основные правила о том, чтобы принимать их в качестве аргумента, - вы все равно должны брать их по ссылке const, если вам действительно не нужна настоящая копия - возьмите по значению тогда.
Ответ 3
Помните, что если вы не проходите в r-значении, то передача по значению приведет к полномасштабной копии. Так что, вообще говоря, переход по значению может привести к поражению производительности.
Ответ 4
С++ 11 перемещение семантики делает передачу и возврат по значению гораздо более привлекательным даже для сложных объектов.
Образец, который вы даете, однако, является образцом прохода по значению
int hd(vector<int> a) {
Итак, С++ 11 не влияет на это.
Даже если вы правильно объявили 'hd', чтобы взять rvalue
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 been moved.
}
Как правило:
- Перейдите по значению для тривиальных объектов,
- Передайте по значению, если получателю нужна изменчивая копия,
- Передайте по значению, если вам всегда нужно сделать копию,
- Передавать по ссылке const для нетривиальных объектов, где зрителю требуется только просмотр содержимого/состояния, но он не нуждается в его модификации,
- Переместить, когда получателю нужна изменчивая копия временного/сконструированного значения (например,
std::move(std::string("a") + std::string("b")))
.
- Перемещение, когда вам требуется локальность состояния объекта, но вы хотите сохранить существующие значения/данные и освободить текущий держатель.
Ответ 5
Ваш пример испорчен. С++ 11 не дает вам двигаться с кодом, который у вас есть, и будет сделана копия.
Тем не менее, вы можете получить ход, объявив функцию, чтобы взять ссылку rvalue, а затем передать ее:
int hd(vector<int>&& a) {
return a[0];
}
// ...
std::vector<int> a = ...
int x = hd(std::move(a));
Предположим, что вы больше не будете использовать переменную a
в своей функции, кроме как уничтожить ее или присвоить ей новое значение. Здесь std::move
выводит значение на ссылку rvalue, позволяя двигаться.
Ссылки на константу позволяют временно создавать временные файлы. Вы можете передать то, что подходит для неявного конструктора, и будет создано временное. Классическим примером является массив char, преобразованный в 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});