Pourquoi est-il possible de renvoyer un «vecteur» à partir d'une fonction?

108

Veuillez considérer ce code. J'ai vu ce type de code plusieurs fois. wordsest un vecteur local. Comment est-il possible de le renvoyer depuis une fonction?

Pouvons-nous garantir qu'il ne mourra pas?

 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
la source
18
Il est copié au retour.
songyuanyao
6
Personne ne garantit .. Il va mourir, mais après sa copie.
Maroun
7
Vous n'avez un problème que si votre fonction renvoie une référence:std::vector<std::string>&
Caduchon
14
@songyuanyao non, il sera déplacé.
droite
15
@songyuanyao Oui. C ++ 11 est la norme actuelle, donc C ++ 11 est C ++.
droite

Réponses:

68

Pouvons-nous garantir qu'il ne mourra pas?

Tant qu'aucune référence n'est renvoyée, c'est parfaitement bien de le faire. wordssera déplacé vers la variable recevant le résultat.

La variable locale sera hors de portée. après avoir été déplacé (ou copié).

πάντα ῥεῖ
la source
2
Mais est-ce que le vecteur est efficace ou a des problèmes de performances pour un vecteur qui peut contenir 1000 entrées?
zar
@zadane C'était en question? J'ai également mentionné le déplacement qui évitera de prendre une copie de la valeur de retour réellement (disponible au moins avec la norme actuelle).
πάντα ῥεῖ
2
Non pas vraiment dans la question mais je cherchais une réponse de ce point de vue indépendamment. Je ne sais pas si je publie ma question, j'ai peur qu'ils la marqueront en double :)
zar
@zadane "J'ai peur qu'ils le marquent en double de ceci" Pourrait bien l'être. Jetez un œil à la réponse aux votes les plus élevés . Même pour les implémentations plus anciennes, ne vous inquiétez pas, celles-ci seront de toute façon optimisées correctement par ces compilateurs.
πάντα ῥεῖ
107

Pré C ++ 11:

La fonction ne renverra pas la variable locale, mais plutôt une copie de celle-ci. Votre compilateur peut cependant effectuer une optimisation où aucune action de copie réelle n'est effectuée.

Voir cette question et réponse pour plus de détails.

C ++ 11:

La fonction déplacera la valeur. Voir cette réponse pour plus de détails.

Tim Meyer
la source
2
Il sera déplacé, pas copié. Ceci est garanti.
droite
1
Cela s'applique-t-il également à C ++ 10?
Tim Meyer
28
Le C ++ 10 n'existe pas.
droite
C ++ 03 n'avait pas de sémantique de déplacement (mais la copie peut avoir été éludée, cependant), mais C ++ est C ++ 11 et la question portait sur C ++.
droite
19
Il existe une balise distincte pour les questions exclusives à C ++ 11. Beaucoup d'entre nous, en particulier les programmeurs des grandes entreprises, sont toujours bloqués par des compilateurs qui ne prennent pas encore totalement en charge C ++ 11. J'ai mis à jour la question pour qu'elle soit exacte pour les deux normes.
Tim Meyer
26

Je pense que vous faites référence au problème en C (et C ++) selon lequel le retour d'un tableau à partir d'une fonction n'est pas autorisé (ou du moins ne fonctionnera pas comme prévu) - c'est parce que le retour du tableau sera (si vous l'écrivez dans le formulaire simple) renvoie un pointeur vers le tableau réel sur la pile, qui est ensuite rapidement supprimé lorsque la fonction est renvoyée.

Mais dans ce cas, cela fonctionne, car le std::vectorest une classe et les classes, comme les structures, peuvent (et seront) copiées dans le contexte des appelants. [En fait, la plupart des compilateurs optimiseront ce type particulier de copie en utilisant quelque chose appelé "Optimisation de la valeur de retour", spécialement introduit pour éviter de copier des objets volumineux lorsqu'ils sont renvoyés par une fonction, mais c'est une optimisation, et du point de vue des programmeurs, cela va se comportent comme si le constructeur d'affectation était appelé pour l'objet]

Tant que vous ne renvoyez pas de pointeur ou de référence à quelque chose qui se trouve dans la fonction de retour, tout va bien.

Mats Petersson
la source
13

Pour bien comprendre le comportement, vous pouvez exécuter ce code:

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

Le résultat est le suivant:

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

Notez que cet exemple a été fourni dans un contexte C ++ 03, il pourrait être amélioré pour C ++> = 11

Caduchon
la source
1
Cet exemple serait plus complet s'il incluait également un constructeur de déplacement et un opérateur d'affectation de déplacement, et pas seulement un constructeur de copie et un opérateur d'affectation de copie. (Si les fonctions de déplacement ne sont pas présentes, celles de copie seront utilisées à la place.)
Some Guy
@SomeGuy Je suis d'accord, mais je n'utilise pas C ++ 11. Je ne peux pas fournir de connaissances que je n'ai pas. J'ajoute une note. N'hésitez pas à ajouter une réponse pour C ++> = 11. :-)
Caduchon
-5

Je ne suis pas d'accord et ne recommande pas de retourner 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;
}

C'est beaucoup plus rapide:

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

J'ai testé sur Visual Studio 2017 avec les résultats suivants en mode version:

8.01 MOPs par référence
5.09 MOPs renvoyant le vecteur

En mode débogage, les choses sont bien pires:

0,053 MOPS par référence
0,034 MOP par vecteur de retour

mathématicien
la source
-10

C'est en fait un échec de conception. Vous ne devriez pas utiliser de valeur de retour pour tout ce qui n'est pas une primitive pour tout ce qui n'est pas relativement trivial.

La solution idéale doit être implémentée via un paramètre de retour avec une décision sur la référence / le pointeur et l'utilisation correcte d'un "const \ 'y \' ness" comme descripteur.

En plus de cela, vous devez vous rendre compte que l'étiquette sur un tableau en C et C ++ est effectivement un pointeur et que son abonnement est en fait un offset ou un symbole d'addition.

Ainsi, le label ou ptr array_ptr === array label retournant donc foo [offset] signifie vraiment return element à l'emplacement du pointeur mémoire foo + offset du type return type.

Newbstarr
la source
5
..........quelle. Il semble clair que vous n'êtes pas qualifié pour lancer des accusations telles que «l'échec de la conception». En fait, la promotion de la sémantique de la valeur par des opérations RVO et déplacement est l' un des les principaux succès es de style C ++ moderne. Mais vous semblez être coincé en pensant aux tableaux bruts et aux pointeurs, donc je ne m'attendrais pas à ce que vous compreniez cela.
underscore_d