Когда константная ссылка лучше, чем передача по значению в 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});