Чому добре повернути 'вектор' з функції?


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 записів?
зар

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

2
Ні, не дуже в питанні, але я шукав відповідь з цього погляду незалежно. Я не знаю, чи опублікую це запитання, боюся, що вони позначать його дублікатом цього :)
зар

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

107

Попередньо C ++ 11:

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

Дивіться це запитання та відповідь для подальшої інформації.

C ++ 11:

Функція перемістить значення. Дивіться цю відповідь для отримання більш детальної інформації.


2
Він буде переміщений, а не скопійований. Це гарантовано.
праворуч

1
Чи застосовується це і для C ++ 10?
Тім Мейєр

28
Немає такого поняття, як C ++ 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
Цей приклад був би більш повним, якби він включав конструктор переміщення і також оператор присвоєння переміщення, а не лише конструктор копій та оператор призначення копії. (Якщо функцій переміщення немає, замість них будуть використані копії.)
Деякі хлопці

@SomeGuy Я згоден, але C ++ 11 не використовую. Я не можу надати знань, яких у мене немає. Додаю замітку. Не соромтеся додати відповідь на C ++> = 11. :-)
Caduchon

-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 МОП за посиланням
5,09 МОП, що повертає вектор

У режимі налагодження все набагато гірше:

0.053 МОП за посиланням
0,034 МОП за поверненим вектором


-10

Це насправді провал дизайну. Ви не повинні використовувати значення повернення для чогось не примітивного для чогось, що не є відносно тривіальним.

Ідеальне рішення має бути реалізовано через параметр повернення з рішенням про посилання / покажчик та належним використанням "const \ 'y \' ness" в якості дескриптора.

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

Отже, мітка або ptr array_ptr === мітка масиву таким чином повертає foo [offset] насправді говорить елемент return у місці розташування вказівника пам'яті foo + зміщення типу повернення типу.


5
..........що. Здається зрозуміло, що ти не вмієш кидати звинувачення на кшталт "провалу дизайну". І справді, просування цінностей семантики РВА і операції переміщення є одним з самих головних успіху ес сучасного стилю C ++. Але ви, здається, застрягли, думаючи про необроблені масиви та покажчики, тому я б не очікував, що ви це зрозумієте.
підкреслити_10
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.