С++: избегать копирования с помощью оператора "return"
У меня есть очень простой вопрос в С++.
Как избежать копирования при возврате объекта?
Вот пример:
std::vector<unsigned int> test(const unsigned int n)
{
std::vector<unsigned int> x;
for (unsigned int i = 0; i < n; ++i) {
x.push_back(i);
}
return x;
}
Как я понимаю, как работает С++, эта функция создаст 2 вектора: локальный (x) и копию x, которая будет возвращена. Есть ли способ избежать копирования? (и я не хочу возвращать указатель на объект, но сам объект)
Большое спасибо.
EDIT: дополнительный вопрос в соответствии с первыми ответами: какой будет синтаксис этой функции с использованием "семантики перемещения"?
Ответы
Ответ 1
Эта программа может воспользоваться преимуществами именованной оптимизации возвращаемого значения (NRVO). См. Здесь: http://en.wikipedia.org/wiki/Copy_elision
В С++ 11 есть конструкторы перемещения и присваивания, которые также являются дешевыми. Вы можете прочитать учебник здесь: http://thbecker.net/articles/rvalue_references/section_01.html
Ответ 2
Кажется, возникает некоторая путаница в отношении того, как работает RVO (Оптимизация возвращаемого значения).
Простой пример:
#include <iostream>
struct A {
int a;
int b;
int c;
int d;
};
A create(int i) {
A a = {i, i+1, i+2, i+3 };
std::cout << &a << "\n";
return a;
}
int main(int argc, char*[]) {
A a = create(argc);
std::cout << &a << "\n";
}
И его вывод на ideone:
0xbf928684
0xbf928684
Удивительно?
Собственно, это эффект RVO: возвращаемый объект создается непосредственно на месте в вызывающем.
Как?
Традиционно вызывающий (main
здесь) зарезервирует некоторое пространство в стеке для возвращаемого значения: слот возврата; вызывающий (create
здесь) передается (каким-то образом) адресом слота возврата для копирования его возвращаемого значения в. Затем вызывающая сторона выделяет свое собственное пространство для локальной переменной, в которой он создает результат, как и для любой другой локальной переменной, а затем копирует его в обратный слот в инструкции return
.
RVO запускается, когда компилятор выводит из кода, что переменная может быть сконструирована непосредственно в слоте возврата с эквивалентной семантикой (правило as-if).
Обратите внимание, что это такая общая оптимизация, что она явно указана белым по стандарту, и компилятору не нужно беспокоиться о возможных побочных эффектах конструктора копирования (или перемещения).
Когда?
Компилятор, скорее всего, будет использовать простые правила, например:
// 1. works
A unnamed() { return {1, 2, 3, 4}; }
// 2. works
A unique_named() {
A a = {1, 2, 3, 4};
return a;
}
// 3. works
A mixed_unnamed_named(bool b) {
if (b) { return {1, 2, 3, 4}; }
A a = {1, 2, 3, 4};
return a;
}
// 4. does not work
A mixed_named_unnamed(bool b) {
A a = {1, 2, 3, 4};
if (b) { return {4, 3, 2, 1}; }
return a;
}
В последнем случае (4) оптимизация не может быть применена, когда возвращается A
, потому что компилятор не может построить A
в слоте возврата, поскольку это может потребоваться для чего-то другого (в зависимости от логического условия b
).
Простое эмпирическое правило:
RVO следует применять, если ни один другой кандидат для слота возврата не был объявлен до оператора return
.
Ответ 3
Именованная оптимизация возвращаемого значения выполнит эту работу для вас, поскольку компилятор пытается устранить избыточные вызовы Copy и вызовы Destructor при использовании.
std::vector<unsigned int> test(const unsigned int n){
std::vector<unsigned int> x;
return x;
}
...
std::vector<unsigned int> y;
y = test(10);
с оптимизацией возвращаемого значения:
- y создан
- x создан
- x назначается в y
- x уничтожается
(в случае, если вы хотите попробовать сами для более глубокого понимания, посмотрите этот пример моего)
или даже лучше, так как Matthieu M. указал, что если вы вызываете test
в той же строке, где объявлен y
, вы также можете избежать построение избыточного объекта и избыточное присваивание (x
будет построено в памяти, где y
будет сохранено):
std::vector<unsigned int> y = test(10);
проверьте его ответ, чтобы лучше понять эту ситуацию (вы также узнаете, что такая оптимизация не всегда может быть применена).
ИЛИ, вы можете изменить свой код, чтобы передать ссылку на вектор в вашу функцию, что было бы семантически более правильным, избегая копирования:
void test(std::vector<unsigned int>& x){
// use x.size() instead of n
// do something with x...
}
...
std::vector<unsigned int> y;
test(y);
Ответ 4
Компиляторы часто могут оптимизировать дополнительную копию для вас (это называется оптимизацией возвращаемого значения). См. https://isocpp.org/wiki/faq/ctors#return-by-value-optimization
Ответ 5
Ссылка на него будет работать.
Void(vector<> &x) {
}
Ответ 6
Прежде всего, вы можете объявить свой тип возврата std::vector, и в этом случае вместо копии будет возвращена ссылка.
Вы также можете определить указатель, создать указатель внутри тела метода и затем вернуть этот указатель (или копию этого указателя, чтобы быть верным).
Наконец, многие компиляторы С++ могут выполнять оптимизацию возвращаемого значения (http://en.wikipedia.org/wiki/Return_value_optimization), устраняя в некоторых случаях временный объект.