Возвращает ли оператор значения копирования - PullRequest
62 голосов
/ 07 октября 2009

Мне интересно об этом из-за проблем с областью применения. Например, рассмотрим код

typedef struct {
    int x1;/*top*/
    int x2;/*bottom*/
    int id;
} subline_t;



subline_t subline(int x1, int x2, int id) {
    subline_t t = { x1, x2, id };
    return t;
}

int main(){
    subline_t line = subline(0,0,0); //is line garbage or isn't it? the reference
    //to subline_t t goes out of scope, so the only way this wouldn't be garbage
    //is if return copies
}

Итак, мой вопрос: будет ли копия возврата всегда копироваться? В этом случае это, кажется, работает, поэтому я склонен полагать, что возвращение копирует. Если он будет копировать, будет ли он копировать в каждом случае?

Ответы [ 11 ]

40 голосов
/ 07 октября 2009

Да, в этом случае будет сделана копия. Если вы измените объявление функции следующим образом:

subline_t &subline(int x1, int x2, int id) {

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

Это связано с общей Оптимизацией возвращаемого значения для C ++, которая может избежать реальной операции копирования в описанном вами случае. Конечный результат (или должен быть) такой же, как если бы копия была сделана, но вы должны знать об оптимизации. Наличие этой оптимизации может, в некоторых случаях, изменить наблюдаемое поведение программы.

4 голосов
/ 07 октября 2009

В вашем случае вернется копия

Если ваш код был

subline_t& subline(int, int)

тогда он вернет ссылку, что приведет к неопределенному поведению.

3 голосов
/ 07 октября 2009

Всегда будет возвращать копию.

Если вы хотите избежать снижения производительности при копировании объекта по возвращении, вы можете объявить указатель, создать экземпляр объекта, используя new, и вернуть указатель. В этом случае указатель будет скопирован, но объект не будет.

3 голосов
/ 07 октября 2009

Да, для функции, объявленной для возврата struct, return такой структуры скопирует ее (хотя компилятор уполномочен оптимизировать копию, особенно в тех случаях, когда он может доказать, что оптимизация семантически безвредна , вы можете рассуждать так, «как будто» копирование было гарантировано).

Однако, поскольку вы отметили это как C ++, а не как C, почему бы не предоставить вашему struct конструктор вместо этого ...? Кажется яснее и прямее ...! -)

2 голосов
/ 07 октября 2009

да, возврат - копия

subline_t subline(int x1, int x2, int id) {
        subline_t t = { x1, x2, id };
        return t;
}

Если поставить ссылку, то это не копия

subline_t & subline(int x1, int x2, int id) {
        subline_t t = { x1, x2, id };
        return t; // will result in corruption because returning a reference
}
1 голос
/ 07 октября 2009

Возвращает копию, что вы и хотите. Изменение его для возврата ссылки приведет к неопределенному поведению в присваивании строке.

Однако идиоматический способ сделать это в C ++ - использовать конструкторы и списки назначений. Это лучше инкапсулирует код и структуры данных и позволяет избежать множества промежуточных объектов, которые компиляторы могут создавать / уничтожать / копировать.

struct subline_t {
        int x1;/*top*/
        int x2;/*bottom*/
        int id;

// constructor which initialises values with assignment list.
  subline_t(int the_x1, int the_x2, int the_id) :
    x1(the_x1),
    x2(the_x2),
    id(the_id)
  {
  }
};


int main(){
    subline_t line2(0,0,0); // never requires a copy or assignment.
}
1 голос
/ 07 октября 2009

Получение объектов в C ++ выполняется по значению, а не по ссылке.

ссылка на subline_t t выходит за рамки

Нет, объект скопирован.

будет ли оператор возврата всегда копировать

Да, а не ... Семантически это ведет себя как копирование, но есть нечто, называемое оптимизацией возвращаемого значения, которое сохраняет конструктор копирования.

foo make_foo()
{
    foo f(1,2,3);
    return f;
}

foo ff=make_foo(); /// ff created as if it was created with ff(1,2,3) -- RVO
foo ff2;
ff2=make_foo(); /// instance of foo created and then copied to ff2 and then old
                /// instance destroyed
0 голосов
/ 15 ноября 2018

Я не согласен и НЕ РЕКОМЕНДУЮ возвращать вектор для изменения значений: Гораздо быстрее передать в качестве ссылки:

void vectorial(vector <double> a, vector <double> b, vector <double> &c)
{
    c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
}
//This is slow!!!:
vector <double> vectorial(vector <double> a, vector <double> b)
{
    vector <double> c{ a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] };
    return c;
}

Я тестировал на VS2015 со следующими результатами в режиме выпуска:

По ссылке: 8,01 MOP и вектор возврата: 5,09 MOP на 60% хуже!

В режиме отладки все намного хуже:

По ссылке: 0,053 МОП и вектор возврата: 0,034 МОП

0 голосов
/ 04 августа 2017

Возвращаемый класс или структура могут копироваться или не копироваться, в зависимости от того, использует ли компилятор разрешение на копирование. См. Ответы на Что такое оптимизация копирования и возвращаемое значение? Короче говоря, копируется или нет, зависит от ряда вещей.

Конечно, вы можете избежать копирования, вернув ссылку. В вашем примере возвращение ссылки недопустимо (хотя компилятор это допустит), потому что локальная структура размещена в стеке, и поэтому возвращенная ссылка ссылается на освобожденный объект. Однако, если объект был передан вашей функции (напрямую или как член объекта), вы можете безопасно вернуть ссылку на него и избежать копирования при возврате.

Наконец, если вы не можете доверять копии elision и хотите избежать копирования, вы можете использовать и возвращать unique_ptr вместо ссылки. Сам объект не будет скопирован, хотя сам unique_ptr может быть или не быть (опять же, в зависимости от разрешения копирования!). Однако копирование / перемещение unique_ptr обходится очень дешево, если по какой-то причине исключение копирования unique_ptr не происходит.

Вот пример использования unique_ptr:

#include <memory>

struct A {
public:
  int x;
  int y;

  A(int x, int y) : x(x), y(y) {
  }
};

std::unique_ptr<A> returnsA() {
  return std::make_unique<A>(3, 4);
}

int main() {
  auto a = returnsA();
}

Обратите внимание, что вы (к сожалению) должны объявить конструктор для вашей структуры, иначе make_unique не будет компилироваться из-за недостатков C ++.

0 голосов
/ 07 октября 2009

Только к вашему сведению, поскольку в этом случае вы используете только структуру (ure), это такое же поведение, как и в языке C.

Хотя это языковая функция, рекомендуется, чтобы она не использовалась

...