Warum ist es in Ordnung, einen 'Vektor' von einer Funktion zurückzugeben?

108

Bitte beachten Sie diesen Code. Ich habe diese Art von Code mehrmals gesehen. wordsist ein lokaler Vektor. Wie ist es möglich, es von einer Funktion zurückzugeben?

Können wir garantieren, dass es nicht stirbt?

 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;
}
Pranit Kothari
quelle
18
Es wird bei der Rückkehr kopiert.
Songyuanyao
6
Niemand garantiert .. Es wird sterben, aber nachdem es kopiert wurde.
Maroun
7
Sie haben nur ein Problem, wenn Ihre Funktion eine Referenz zurückgibt:std::vector<std::string>&
Caduchon
14
@songyuanyao nein, es wird verschoben.
Rechtsfalte
15
@songyuanyao Ja. C ++ 11 ist der aktuelle Standard, daher ist C ++ 11 C ++.
Rechtsfalte

Antworten:

68

Können wir garantieren, dass es nicht stirbt?

Solange keine Referenz zurückgegeben wird, ist dies vollkommen in Ordnung. wordswird in die Variable verschoben, die das Ergebnis empfängt.

Die lokale Variable verlässt den Gültigkeitsbereich. nachdem es verschoben (oder kopiert) wurde.

πάντα ῥεῖ
quelle
2
Aber ist das effizient oder haben Leistungsprobleme für Vektoren, die 1000 Einträge enthalten können?
Zar
@ Zadane War das in Frage? Außerdem habe ich einen Umzug erwähnt , bei dem keine Kopie des Rückgabewerts erstellt werden muss (zumindest mit dem aktuellen Standard verfügbar).
πάντα ῥεῖ
2
Nein, nicht wirklich in der Frage, aber ich suchte unabhängig nach einer Antwort aus dieser Perspektive. Ich weiß nicht, ob ich meine Frage poste, ich fürchte, sie werden sie als Duplikat davon markieren :)
zar
@zadane "Ich fürchte, sie werden es als Duplikat davon markieren" Könnte gut sein. Schauen Sie sich einfach die Antwort mit den höheren Stimmen an . Selbst für ältere Implementierungen sollten Sie sich keine Sorgen machen, diese werden von diesen Compilern ohnehin meistens korrekt optimiert.
πάντα ῥεῖ
107

Vor C ++ 11:

Die Funktion gibt nicht die lokale Variable zurück, sondern eine Kopie davon. Ihr Compiler führt jedoch möglicherweise eine Optimierung durch, bei der keine tatsächliche Kopieraktion ausgeführt wird.

Weitere Informationen finden Sie in dieser Frage und Antwort .

C ++ 11:

Die Funktion verschiebt den Wert. Siehe diese Antwort für weitere Details.

Tim Meyer
quelle
2
Es wird verschoben, nicht kopiert. Dies ist garantiert.
Rechtsfalte
1
Gilt das auch für C ++ 10?
Tim Meyer
28
Es gibt kein C ++ 10.
Rechtsfalte
C ++ 03 hatte keine Verschiebungssemantik (aber die Kopie wurde möglicherweise entfernt), aber C ++ ist C ++ 11 und die Frage betraf C ++.
Rechtsfalte
19
Es gibt ein separates Tag für Fragen, die nur für C ++ 11 gelten. Viele von uns, insbesondere Programmierer in größeren Unternehmen, sind immer noch an Compiler gebunden, die C ++ 11 noch nicht vollständig unterstützen. Ich habe die Frage aktualisiert, um für beide Standards korrekt zu sein.
Tim Meyer
26

Ich denke, Sie beziehen sich auf das Problem in C (und C ++), dass das Zurückgeben eines Arrays von einer Funktion nicht zulässig ist (oder zumindest nicht wie erwartet funktioniert) - dies liegt daran, dass das Array zurückgegeben wird (wenn Sie es einschreiben) die einfache Form) gibt einen Zeiger auf das tatsächliche Array auf dem Stapel zurück, der dann sofort entfernt wird, wenn die Funktion zurückkehrt.

In diesem Fall funktioniert es jedoch, da std::vectores sich um eine Klasse handelt und Klassen wie Strukturen in den Aufruferkontext kopiert werden können (und werden). [Tatsächlich optimieren die meisten Compiler diesen speziellen Kopiertyp mithilfe der sogenannten "Rückgabewertoptimierung", die speziell eingeführt wurde, um das Kopieren großer Objekte zu vermeiden, wenn sie von einer Funktion zurückgegeben werden. Dies ist jedoch eine Optimierung und aus Sicht des Programmierers auch verhalten sich so, als ob der Zuweisungskonstruktor für das Objekt aufgerufen wurde]

Solange Sie keinen Zeiger oder Verweis auf etwas zurückgeben, das sich innerhalb der zurückgegebenen Funktion befindet, ist alles in Ordnung.

Mats Petersson
quelle
13

Um das Verhalten gut zu verstehen, können Sie diesen Code ausführen:

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

Die Ausgabe ist die folgende:

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

Beachten Sie, dass dieses Beispiel im C ++ 03-Kontext bereitgestellt wurde und für C ++> = 11 verbessert werden kann

Caduchon
quelle
1
Dieses Beispiel wäre vollständiger, wenn es auch einen Verschiebungskonstruktor und einen Verschiebungszuweisungsoperator enthalten würde und nicht nur einen Kopierkonstruktor und einen Kopierzuweisungsoperator. (Wenn die Verschiebungsfunktionen nicht vorhanden sind, werden stattdessen die
Ein
@SomeGuy Ich stimme zu, aber ich benutze kein C ++ 11. Ich kann kein Wissen liefern, das ich nicht habe. Ich füge eine Notiz hinzu. Fühlen Sie sich frei, eine Antwort für C ++> = 11 hinzuzufügen. :-)
Caduchon
-5

Ich bin nicht einverstanden und empfehle nicht, Folgendes zurückzugeben 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;
}

Das geht viel schneller:

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

Ich habe in Visual Studio 2017 mit den folgenden Ergebnissen im Release-Modus getestet:

8.01 MOPs nach Referenz
5.09 MOPs, die den Vektor zurückgeben

Im Debug-Modus sind die Dinge viel schlimmer:

0,053 MOPS als Referenz
0,034 MOPs als Rückgabevektor

Mathengineer
quelle
-10

Dies ist eigentlich ein Designfehler. Sie sollten keinen Rückgabewert für etwas verwenden, das kein Grundelement für etwas ist, das nicht relativ trivial ist.

Die ideale Lösung sollte durch einen Rückgabeparameter mit einer Entscheidung über Referenz / Zeiger und der richtigen Verwendung einer "Konstante \ 'y \' ness" als Deskriptor implementiert werden.

Darüber hinaus sollten Sie sich darüber im Klaren sein, dass die Beschriftung eines Arrays in C und C ++ effektiv ein Zeiger ist und das Abonnement effektiv ein Offset- oder Additionssymbol ist.

Die Bezeichnung oder ptr array_ptr === Array-Bezeichnung, die foo [offset] zurückgibt, sagt also wirklich return-Element an der Speicherzeigerposition foo + offset vom Typ return type.

Newbstarr
quelle
4
..........Was. Es scheint klar zu sein, dass Sie nicht qualifiziert sind, Anschuldigungen wie "Designfehler" herumzuwerfen. Und in der Tat ist die Förderung der Wert Semantik von RVO und Bewegungsoperationen eines der wichtigsten Erfolgs es der modernen C ++ Stil. Aber Sie scheinen nicht weiter über rohe Arrays und Zeiger nachzudenken, also würde ich nicht erwarten, dass Sie das verstehen.
underscore_d