web-dev-qa-db-fra.com

C ++ façon correcte de renvoyer le pointeur vers le tableau à partir de la fonction

Je suis assez nouveau en C++ et j'évite les pointeurs. D'après ce que j'ai lu en ligne, je ne peux pas retourner un tableau mais je peux y retourner un pointeur. J'ai fait un petit code pour le tester et je me demandais si c'était la façon normale/correcte de le faire:

#include <iostream>
using namespace std;

int* test (int in[5]) {
    int* out = in;
    return out;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* pArr = test(arr);
    for (int i = 0; i < 5; i++) cout<<pArr[i]<<endl;
    cout<<endl;
    return 0;
}

Edit: Cela ne semble pas être bon. Comment dois-je le réécrire?

int* test (int a[5], int b[5]) {
    int c[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    int* out = c;
    return out;
}
23
asimes

Votre code tel qu'il est est correct mais j'ai du mal à comprendre comment il pourrait/serait utilisé dans un scénario réel. Cela dit, veuillez noter quelques mises en garde lors du retour de pointeurs à partir de fonctions:

  • Lorsque vous créez un tableau avec la syntaxe int arr[5];, Il est alloué sur la pile et est local à la fonction.
  • C++ vous permet de renvoyer un pointeur sur ce tableau, mais c'est comportement indéfini pour utiliser la mémoire pointée par ce pointeur en dehors de sa portée locale. Lisez cette excellente réponse en utilisant une analogie du monde réel pour obtenir une compréhension beaucoup plus claire que ce que je pourrais jamais expliquer.
  • Vous pouvez toujours utiliser le tableau en dehors de la portée si vous pouvez garantir que la mémoire du tableau n'a pas été purgée. Dans votre cas, cela est vrai lorsque vous passez arr à test().
  • Si vous voulez faire circuler des pointeurs vers un tableau alloué dynamiquement sans vous soucier des fuites de mémoire, vous devriez faire quelques lectures sur std::unique_ptr/std::shared_ptr<>.

Edit - pour répondre au cas d'utilisation de la multiplication matricielle

Vous avez deux options. La manière naïve est d'utiliser std::unique_ptr/std::shared_ptr<>. La méthode C++ moderne consiste à avoir une classe Matrix où vous surchargez operator * Et vous devez absolument utiliser le nouveau rvalue references Si vous voulez éviter de copier le résultat de la multiplication pour obtenir hors de la fonction. En plus d'avoir vos copy constructor, operator = Et destructor, vous devez également avoir move constructor Et move assignment operator. Parcourez les questions et réponses de cette recherche pour avoir plus d'informations sur la façon d'y parvenir.

Edit 2 - réponse à la question jointe

int* test (int a[5], int b[5]) {
    int *c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

Si vous l'utilisez comme int *res = test(a,b);, puis quelque temps plus tard dans votre code, vous devez appeler delete []res Pour libérer la mémoire allouée dans la fonction test(). Vous voyez maintenant que le problème est qu'il est extrêmement difficile de garder une trace manuelle du moment où faire l'appel à delete. D'où les approches sur la façon de le gérer, telles que décrites dans la réponse.

16
vvnraman

Votre code est OK. Notez cependant que si vous renvoyez un pointeur vers un tableau et que ce tableau sort de la portée, vous ne devez plus utiliser ce pointeur. Exemple:

int* test (void)
{
    int out[5];
    return out;
}

Ce qui précède ne fonctionnera jamais, car out n'existe plus lorsque test() revient. Le pointeur renvoyé ne doit plus être utilisé. Si vous faites l'utilisez, vous lirez/écrivez dans la mémoire que vous ne devriez pas.

Dans votre code d'origine, le tableau arr sort de la portée lorsque main() revient. Évidemment, ce n'est pas un problème, car le retour de main() signifie également que votre programme se termine.

Si vous voulez quelque chose qui restera et ne pourra pas sortir de la portée, vous devez l'allouer avec new:

int* test (void)
{
    int* out = new int[5];
    return out;
}

Le pointeur renvoyé sera toujours valide. N'oubliez pas de le supprimer à nouveau lorsque vous en avez terminé, en utilisant delete[]:

int* array = test();
// ...
// Done with the array.
delete[] array;

Le supprimer est le seul moyen de récupérer la mémoire qu'il utilise.

7
Nikos C.

Nouvelle réponse à une nouvelle question:

Vous ne pouvez pas retourner le pointeur sur la variable automatique (int c[5]) de la fonction. La variable automatique termine sa durée de vie avec un bloc englobant de retour (fonction dans ce cas) - vous renvoyez donc le pointeur vers un tableau non existant.

Soit dynamisez votre variable:

int* test (int a[5], int b[5]) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

Ou modifiez votre implémentation pour utiliser std::array:

std::array<int,5> test (const std::array<int,5>& a, const std::array<int,5>& b) 
{
   std::array<int,5> c;
   for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
   return c;
}

Si votre compilateur ne fournit pas std::array vous pouvez le remplacer par une structure simple contenant un tableau:

struct array_int_5 { 
   int data[5];
   int& operator [](int i) { return data[i]; } 
   int operator const [](int i) { return data[i]; } 
};

Ancienne réponse à l'ancienne question:

Votre code est correct, et ... hmm, eh bien, ... inutile. Étant donné que les tableaux peuvent être attribués à des pointeurs sans fonction supplémentaire (notez que vous l'utilisez déjà dans votre fonction):

int arr[5] = {1, 2, 3, 4, 5};
//int* pArr = test(arr);
int* pArr = arr;

De plus la signature de votre fonction:

int* test (int in[5])

Est équivalent à:

int* test (int* in)

Vous voyez donc que cela n'a aucun sens.

Cependant, cette signature prend un tableau, pas un pointeur:

int* test (int (&in)[5])
2
PiotrNycz

Une variable référençant un tableau est fondamentalement un pointeur sur son premier élément, donc oui, vous pouvez légitimement renvoyer un pointeur sur un tableau, car ce sont essentiellement la même chose. Vérifiez vous-même:

#include <assert.h>

int main() {
  int a[] = {1, 2, 3, 4, 5}; 

  int* pArr = a;
  int* pFirstElem = &(a[0]);

  assert(a == pArr);
  assert(a == pFirstElem);

  return 0;
}

Cela signifie également que en passant un tableau à une fonction doit être fait via un pointeur (et non via int in[5]), et éventuellement avec la longueur du tableau:

int* test(int* in, int len) {
    int* out = in;
    return out;
}

Cela dit, vous avez raison, l'utilisation de pointeurs (sans bien les comprendre) est assez dangereuse. Par exemple, le fait de référencer un tableau qui a été alloué sur la pile et qui est hors de portée donne un comportement non défini :

#include <iostream>

using namespace std;

int main() {
  int* pArr = 0;
  {
    int a[] = {1, 2, 3, 4, 5};
    pArr = a; // or test(a) if you wish
  }
  // a[] went out of scope here, but pArr holds a pointer to it

  // all bets are off, this can output "1", output 1st chapter
  // of "Romeo and Juliet", crash the program or destroy the
  // universe
  cout << pArr[0] << endl; // WRONG!

  return 0;
}

Donc, si vous ne vous sentez pas suffisamment compétent, utilisez simplement std::vector.

[réponse à la question mise à jour]

La bonne façon d'écrire votre fonction test est soit ceci:

void test(int* a, int* b, int* c, int len) {
  for (int i = 0; i < len; ++i) c[i] = a[i] + b[i];
}
...
int main() {
   int a[5] = {...}, b[5] = {...}, c[5] = {};
   test(a, b, c, 5);
   // c now holds the result
}

Ou ceci (en utilisant std::vector):

#include <vector>

vector<int> test(const vector<int>& a, const vector<int>& b) {
  vector<int> result(a.size());
  for (int i = 0; i < a.size(); ++i) {
    result[i] = a[i] + b[i];
  }
  return result; // copy will be elided
}
1
dorserg

Dans une application réelle, la façon dont vous avez renvoyé le tableau est appelée en utilisant un paramètre out. Bien sûr, vous n'êtes pas obligé de renvoyer un pointeur sur le tableau, car l'appelant l'a déjà, il vous suffit de remplir le tableau. Il est également courant de passer un autre argument spécifiant la taille du tableau afin de ne pas le déborder.

L'utilisation d'un paramètre de sortie présente l'inconvénient que l'appelant peut ne pas connaître la taille du tableau pour stocker le résultat. Dans ce cas, vous pouvez renvoyer une instance de classe de tableau std :: vector ou similaire.

0
user1610015

Votre code (qui semble correct) ne renvoie pas de pointeur sur un tableau. Il renvoie un pointeur vers le premier élément de un tableau.

En fait, c'est généralement ce que vous voulez faire. La plupart des manipulations de tableaux se font via des pointeurs vers des éléments individuels, et non via des pointeurs vers le tableau dans son ensemble.

Vous pouvez définir un pointeur vers un tableau, par exemple ceci:

double (*p)[42];

définit p comme un pointeur vers un tableau à 42 éléments de doubles. Un gros problème avec cela est que vous devez spécifier le nombre d'éléments dans le tableau dans le cadre du type - et ce nombre doit être une constante au moment de la compilation. La plupart des programmes qui traitent des tableaux doivent traiter des tableaux de tailles différentes; la taille d'un tableau donné ne variera pas après sa création, mais sa taille initiale n'est pas nécessairement connue au moment de la compilation, et différents objets du tableau peuvent avoir des tailles différentes.

Un pointeur sur le premier élément d'un tableau vous permet d'utiliser l'arithmétique du pointeur ou l'opérateur d'indexation [] pour parcourir les éléments du tableau. Mais le pointeur ne vous dit pas combien d'éléments le tableau a; vous devez généralement en faire le suivi vous-même.

Si une fonction doit créer un tableau et renvoyer un pointeur sur son premier élément, vous devez gérer vous-même le stockage de ce tableau, de plusieurs manières. Vous pouvez demander à l'appelant de passer un pointeur vers (le premier élément) d'un objet tableau, probablement avec un autre argument spécifiant sa taille - ce qui signifie que l'appelant doit savoir la taille du tableau. Ou la fonction peut renvoyer un pointeur vers (le premier élément de) un tableau statique défini à l'intérieur de la fonction - ce qui signifie que la taille du tableau est fixe, et le même tableau sera encombré par un deuxième appel à la fonction. Ou la fonction peut allouer le tableau sur le tas - ce qui rend l'appelant responsable de le désallouer plus tard.

Tout ce que j'ai écrit jusqu'à présent est commun au C et au C++, et en fait, il est beaucoup plus dans le style du C que du C++. La section 6 de la comp.lang.c FAQ traite du comportement des tableaux et des pointeurs en C.

Mais si vous écrivez en C++, vous feriez probablement mieux d'utiliser des idiomes C++. Par exemple, la bibliothèque standard C++ fournit un certain nombre d'en-têtes définissant des classes de conteneur telles que <vector> et <array>, qui s'occupera de la plupart de ces trucs pour vous. Sauf si vous avez une raison particulière d'utiliser des tableaux et des pointeurs bruts, il vaut probablement mieux utiliser à la place des conteneurs C++.

EDIT: Je pense que vous avez modifié votre question pendant que je tapais cette réponse. Le nouveau code à la fin de votre question n'est pas, comme vous l'observateur, bon; elle renvoie un pointeur sur un objet qui cesse d'exister dès que la fonction revient. Je pense que j'ai couvert les alternatives.

0
Keith Thompson

vous pouvez (en quelque sorte) retourner un tableau

au lieu de

int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
int* m3 = test(m1, m2);

écrire

struct mystruct
{
  int arr[5];
};


int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
mystruct m3 = test(m1,m2);

où le test ressemble

struct mystruct test(int m1[5], int m2[5])
{
  struct mystruct s;
  for (int i = 0; i < 5; ++i ) s.arr[i]=m1[i]+m2[i];
  return s;
}

pas très efficace car on copie il délivre une copie du tableau

0
Anders