Perché va bene restituire un "vettore" da una funzione?


108

Si prega di considerare questo codice. Ho visto più volte questo tipo di codice. wordsè un vettore locale. Come è possibile restituirlo da una funzione?

Possiamo garantire che non morirà?

 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
Viene copiato al ritorno.
songyuanyao

6
Nessuno garantisce .. E si muore, ma dopo che è copiato.
Maroun

7
Hai solo un problema se la tua funzione restituisce un riferimento:std::vector<std::string>&
Caduchon

14
@songyuanyao no, verrà spostato.
destra

15
@songyuanyao Sì. C ++ 11 è lo standard corrente, quindi C ++ 11 è C ++.
destra

Risposte:


68

Possiamo garantire che non morirà?

Finché non viene restituito alcun riferimento, è perfettamente corretto farlo. wordsverrà spostato nella variabile che riceve il risultato.

La variabile locale uscirà dall'ambito. dopo che è stato spostato (o copiato).


2
Ma è efficiente o ha qualche problema di prestazioni per il vettore che può contenere 1000 voci?
zar

@zadane Era questo in questione? Ho anche menzionato lo spostamento che eviterà di prendere una copia del valore restituito effettivamente (disponibile almeno con lo standard attuale).
πάντα ῥεῖ

2
No, non proprio nella domanda, ma cercavo una risposta da quella prospettiva in modo indipendente. Non so se posto la mia domanda, temo che la contrassegneranno come duplicata di questa :)
zar

@zadane "Temo che lo contrassegneranno come duplicato di questo" Potrebbe essere. Dai un'occhiata alla risposta più votata . Anche per le implementazioni più vecchie non dovresti preoccuparti, quelle saranno per lo più ottimizzate correttamente da quei compilatori comunque.
πάντα ῥεῖ

107

Pre C ++ 11:

La funzione non restituirà la variabile locale, ma piuttosto una copia di essa. Il compilatore potrebbe tuttavia eseguire un'ottimizzazione in cui non viene eseguita alcuna azione di copia effettiva.

Vedi questa domanda e risposta per ulteriori dettagli.

C ++ 11:

La funzione sposterà il valore. Vedi questa risposta per ulteriori dettagli.


2
Sarà spostato, non copiato. Questo è garantito.
destra

1
Questo vale anche per C ++ 10?
Tim Meyer

28
Non esiste C ++ 10.
destra

C ++ 03 non aveva semantica di spostamento (ma la copia potrebbe essere stata elisa, però), ma C ++ è C ++ 11 e la domanda riguardava C ++.
destra

19
C'è un tag separato per le domande esclusive di C ++ 11. Molti di noi, specialmente i programmatori di aziende più grandi, sono ancora attaccati a compilatori che non supportano ancora completamente C ++ 11. Ho aggiornato la domanda in modo che fosse accurata per entrambi gli standard.
Tim Meyer

26

Penso che ti riferisci al problema in C (e C ++) che la restituzione di un array da una funzione non è consentita (o almeno non funzionerà come previsto) - questo perché l'array restituirà (se lo scrivi in la forma semplice) restituisce un puntatore all'array effettivo nello stack, che viene quindi prontamente rimosso quando la funzione ritorna.

Ma in questo caso, funziona, perché std::vectorè una classe e le classi, come le strutture, possono (e saranno) copiate nel contesto del chiamante. [In realtà, la maggior parte dei compilatori ottimizzerà questo particolare tipo di copia usando qualcosa chiamato "Ottimizzazione del valore di ritorno", introdotto specificamente per evitare di copiare oggetti di grandi dimensioni quando vengono restituiti da una funzione, ma questa è un'ottimizzazione, e dal punto di vista dei programmatori, lo farà comportarsi come se per l'oggetto fosse stato chiamato il costruttore dell'assegnazione]

Finché non restituisci un puntatore o un riferimento a qualcosa che è all'interno della funzione di ritorno, stai bene.


13

Per comprendere bene il comportamento, puoi eseguire questo codice:

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

L'output è il seguente:

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()

Si noti che questo esempio è stato fornito nel contesto C ++ 03, potrebbe essere migliorato per C ++> = 11


1
Questo esempio sarebbe più completo se includesse un costruttore di spostamento e anche un operatore di assegnazione di spostamento e non solo un costruttore di copia e un operatore di assegnazione di copia. (Se le funzioni di spostamento non sono presenti, verranno utilizzate invece quelle di copia.)
Qualcuno

@SomeGuy Sono d'accordo, ma non uso C ++ 11. Non posso fornire conoscenze che non ho. Aggiungo una nota. Sentiti libero di aggiungere una risposta per C ++> = 11. :-)
Caduchon

-5

Non sono d'accordo e non consiglio di restituire un 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;
}

Questo è molto più veloce:

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

Ho provato su Visual Studio 2017 con i seguenti risultati in modalità di rilascio:

8.01 MOP per riferimento
5.09 MOP che restituiscono il vettore

In modalità debug, le cose vanno molto peggio:

0,053 MOPS per riferimento
0,034 MOP per vettore di ritorno


-10

Questo è in realtà un fallimento del design. Non dovresti usare un valore di ritorno per qualcosa che non sia primitivo per qualcosa che non sia relativamente banale.

La soluzione ideale dovrebbe essere implementata attraverso un parametro di ritorno con una decisione sul riferimento / puntatore e l'uso corretto di un "const \ 'y \' ness" come descrittore.

Inoltre, dovresti renderti conto che l'etichetta su un array in C e C ++ è effettivamente un puntatore e la sua sottoscrizione è effettivamente un offset o un simbolo di addizione.

Quindi l'etichetta o ptr array_ptr === array label che restituisce foo [offset] in realtà sta dicendo return element alla posizione del puntatore di memoria foo + offset di tipo return type.


5
..........che cosa. È chiaro che non sei qualificato per lanciare accuse come "fallimento del design". E infatti, la promozione della semantica di valore da operazioni RVO e Move è uno dei i principali successi es della moderna stile C ++. Ma sembra che tu sia bloccato a pensare a matrici e puntatori non elaborati, quindi non mi aspetto che tu lo capisca.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.