Почему можно возвращать «вектор» из функции?


108

Пожалуйста, обратите внимание на этот код. Я видел этот тип кода несколько раз. words- локальный вектор. Как можно вернуть его из функции?

Можем ли мы гарантировать, что он не умрет?

 std::vector<std::string> read_file(const std::string& path)
 {
    std::ifstream file("E:\\names.txt");

    if (!file.is_open())
    {
        std::cerr << "Unable to open file" << "\n";
        std::exit(-1);
    }

    std::vector<string> words;//this vector will be returned
    std::string token;

    while (std::getline(file, token, ','))
    {
        words.push_back(token);
    }

    return words;
}

18
Он копируется при возврате.
songyuanyao

6
Никто не гарантирует , .. Это не умрет, но после того, как он скопирован.
Maroun

7
Проблема возникает только в том случае, если ваша функция возвращает ссылку:std::vector<std::string>&
Caduchon

14
@songyuanyao нет, он будет перемещен.
правый фолд

15
@songyuanyao Да. C ++ 11 является текущим стандартом, поэтому C ++ 11 - это C ++.
правый фолд

Ответы:


68

Можем ли мы гарантировать, что он не умрет?

Пока ссылка не возвращается, это нормально. wordsбудет перемещен в переменную, получающую результат.

Локальная переменная выйдет за рамки. после того, как он был перемещен (или скопирован).


2
Но насколько эффективны или есть какие-либо проблемы с производительностью, скажем, для вектора, который может содержать 1000 записей?
zar

@zadane Это было под вопросом? Также я упомянул перемещение, которое позволит избежать фактического получения копии возвращаемого значения (доступного, по крайней мере, с текущим стандартом).
πάντα ῥεῖ

2
Нет, не совсем в этом вопросе, но я искал ответ с этой точки зрения независимо. Не знаю, опубликую ли я свой вопрос, боюсь, они отметят его как дубликат этого :)
zar

@zadane "Боюсь, они отметят это как дубликат" Вполне может быть. Просто взгляните на ответ, получивший более высокую оценку . Не стоит беспокоиться даже о более старых реализациях, они в любом случае будут корректно оптимизированы этими компиляторами.
πάντα ῥεῖ

107

До C ++ 11:

Функция вернет не локальную переменную, а ее копию. Однако ваш компилятор может выполнить оптимизацию, при которой фактическое действие копирования не выполняется.

См. Этот вопрос и ответ для получения дополнительной информации.

С ++ 11:

Функция переместит значение. См. Этот ответ для получения дополнительных сведений.


2
Он будет перемещен, а не скопирован. Это гарантировано.
правый фолд

1
Применимо ли это и к C ++ 10?
Тим Мейер

28
С ++ 10 не существует.
правый фолд

В C ++ 03 не было семантики перемещения (хотя копирование могло быть опущено), но C ++ - это C ++ 11, и вопрос был о C ++.
правый фолд

19
Для вопросов, относящихся исключительно к C ++ 11, есть отдельный тег. Многие из нас, особенно программисты в крупных компаниях, все еще придерживаются компиляторов, которые еще не полностью поддерживают C ++ 11. Я обновил вопрос, чтобы он соответствовал обоим стандартам.
Тим Мейер

26

Я думаю, вы имеете в виду проблему в C (и C ++), что возврат массива из функции недопустим (или, по крайней мере, не будет работать должным образом) - это потому, что возврат массива будет (если вы напишете его в простая форма) возвращают указатель на фактический массив в стеке, который затем сразу же удаляется при возврате функции.

Но в этом случае это работает, потому что std::vectorэто класс, а классы, как и структуры, могут (и будут) копироваться в контекст вызывающего. [На самом деле, большинство компиляторов оптимизируют этот конкретный тип копии, используя так называемую «оптимизацию возвращаемого значения», специально введенную, чтобы избежать копирования больших объектов, когда они возвращаются из функции, но это оптимизация, и с точки зрения программистов, это будет вести себя так, как если бы для объекта был вызван конструктор присваивания]

Пока вы не возвращаете указатель или ссылку на что-то, что находится внутри возвращаемой функции, все в порядке.


13

Чтобы хорошо понять поведение, вы можете запустить этот код:

#include <iostream>

class MyClass
{
  public:
    MyClass() { std::cout << "run constructor MyClass::MyClass()" << std::endl; }
    ~MyClass() { std::cout << "run destructor MyClass::~MyClass()" << std::endl; }
    MyClass(const MyClass& x) { std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl; }
    MyClass& operator = (const MyClass& x) { std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl; }
};

MyClass my_function()
{
  std::cout << "run my_function()" << std::endl;
  MyClass a;
  std::cout << "my_function is going to return a..." << std::endl;
  return a;
}

int main(int argc, char** argv)
{
  MyClass b = my_function();

  MyClass c;
  c = my_function();

  return 0;
}

Результат следующий:

run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run constructor MyClass::MyClass()
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run assignation MyClass::operator=(const MyClass&)
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()

Обратите внимание, что этот пример был предоставлен в контексте C ++ 03, его можно улучшить для C ++> = 11


1
Этот пример был бы более полным, если бы он также включал конструктор перемещения и оператор присваивания перемещения, а не только конструктор копирования и оператор присваивания копии. (Если функций перемещения нет, вместо них будут использоваться копии.)
Some Guy

@SomeGuy Согласен, но я не использую C ++ 11. Я не могу предоставить знания, которых у меня нет. Добавляю примечание. Не стесняйтесь добавлять ответ для C ++> = 11. :-)
Кадучон

-5

Я не согласен и не рекомендую возвращать vector:

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;
}

Это намного быстрее:

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];
}

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

8.01 MOP по ссылке
5.09 MOP, возвращающий вектор

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

0,053 MOPS по ссылке
0,034 MOP по вектору возврата


-10

На самом деле это провал дизайна. Вы не должны использовать возвращаемое значение для чего-либо, кроме примитивов, для чего-либо, что не является относительно тривиальным.

Идеальное решение должно быть реализовано через возвращаемый параметр с решением относительно ссылки / указателя и правильного использования «const \ 'y \' ness» в качестве дескриптора.

Кроме того, вы должны понимать, что метка на массиве в C и C ++ фактически является указателем, а его подписка фактически является символом смещения или сложения.

Таким образом, метка или ptr array_ptr === метка массива, возвращающая foo [смещение], на самом деле говорит о возвращаемом элементе в месте указателя памяти foo + смещение типа возвращаемого типа.


5
..........какие. Кажется очевидным, что вы не имеете права выдвигать обвинения вроде «неудачного дизайна». И в самом деле, продвижение ценностей семантики РВА и операции перемещения является одним из самых главных успеха эс современного стиля C ++. Но вы, кажется, застряли, думая о необработанных массивах и указателях, поэтому я не ожидал, что вы это поймете.
underscore_d
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.