Подтвердить что ты не робот

С++: избегать копирования с помощью оператора "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: дополнительный вопрос в соответствии с первыми ответами: какой будет синтаксис этой функции с использованием "семантики перемещения"?

4b9b3361

Ответ 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), устраняя в некоторых случаях временный объект.