Question

J'essaie de comprendre les n-uplets (merci @litb), et on suggère généralement de les utiliser pour les fonctions retournant > 1 valeur.

C’est quelque chose pour lequel j’utiliserais normalement une structure, et je ne comprends pas les avantages des n-uplets dans ce cas - c’est une approche sujette aux erreurs pour les fainéants en bout de chaîne.

En empruntant un exemple , j'utiliserais ceci

struct divide_result {
    int quotient;
    int remainder;
};

En utilisant un tuple, vous auriez

typedef boost::tuple<int, int> divide_result;

Mais sans lire le code de la fonction que vous appelez (ni les commentaires, si vous êtes assez bête pour leur faire confiance), vous ne savez pas quel int est quotient et vice-versa. Cela ressemble plutôt à ...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

... qui ne me remplirait pas de confiance.

Alors, quels sont les avantages des n-uplets sur les structures qui compensent l'ambiguïté?

Était-ce utile?

La solution

tuples

Je pense que je suis d'accord avec vous pour dire que la question de savoir quelle position correspond à quelle variable peut créer de la confusion. Mais je pense qu'il y a deux côtés. L’un est le côté appel et l’autre le côté appelé :

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

Je pense que ce que nous avons obtenu est parfaitement clair, mais cela peut devenir déroutant si vous devez renvoyer plus de valeurs à la fois. Une fois que le programmeur de l'appelant a consulté la documentation de div, il saura quelle position correspond à quoi et pourra écrire du code efficace. En règle générale, je dirais de ne pas renvoyer plus de 4 valeurs à la fois. Pour tout ce qui va au-delà, préférez un struct.

paramètres de sortie

Les paramètres de sortie peuvent également être utilisés, bien sûr:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

Maintenant, je pense que cela montre à quel point les n-uplets sont meilleurs que les paramètres de sortie. Nous avons mélangé l’entrée de operator>> avec sa sortie, sans gagner aucun avantage. Pire encore, nous laissons le lecteur de ce code dans des doutes sur ce qui pourrait être la valeur de retour réelle de tie be. Il y a de merveilleux exemples dans lesquels les paramètres de sortie sont utiles. À mon avis, vous ne devriez les utiliser que lorsque vous n'avez pas d'autre moyen, car la valeur de retour est déjà prise et ne peut pas être changée en tuple ni en struct. <=> est un bon exemple d'utilisation des paramètres de sortie, car la valeur de retour est déjà réservée pour le flux. Vous pouvez ainsi chaîner <=> des appels. Si vous n'avez rien à faire avec les opérateurs et que le contexte n'est pas limpide, je vous recommande d'utiliser des pointeurs pour signaler du côté de l'appel que l'objet est réellement utilisé comme paramètre de sortie, en plus des commentaires, le cas échéant. / p>

renvoyer une structure

La troisième option consiste à utiliser une structure:

div_result d = div(10, 3);

Je pense que cela remporte définitivement le prix pour la clarté . Mais notez que vous devez toujours accéder au résultat dans cette structure, et le résultat n'est pas & "Mis à nu" & "; sur la table, comme c'était le cas pour les paramètres de sortie et le tuple utilisé avec <=>.

Je pense qu’un point important de nos jours est de tout rendre aussi générique que possible. Donc, disons que vous avez une fonction qui peut imprimer des n-uplets. Vous pouvez simplement faire

cout << div(10, 3);

Et que votre résultat soit affiché. Je pense que les tuples, de l’autre côté, gagnent clairement pour leur nature polyvalente . En faisant cela avec div_result, vous devez surcharger l'opérateur & Lt; & Lt ;, ou devez afficher chaque membre séparément.

Autres conseils

Une autre option consiste à utiliser une carte Boost Fusion (code non testé):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

Vous pouvez accéder aux résultats de manière relativement intuitive:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

Il existe également d'autres avantages, tels que la possibilité de parcourir les champs de la carte, etc. Voir le doco pour plus d'informations.

Avec les n-uplets, vous pouvez utiliser tie, ce qui est parfois très utile: std::tr1::tie (quotient, remainder) = do_division ();. Ce n'est pas si facile avec les structs. Deuxièmement, lorsqu’on utilise un code de modèle, il est parfois plus facile de s’appuyer sur des paires que d’ajouter un autre typedef pour le type struct.

Et si les types sont différents, alors une paire / un tuple n’est vraiment pas pire qu’une structure. Pensons par exemple pair<int, bool> readFromFile(), où int est le nombre d'octets lus et bool indique si l'eof a été touché. Ajouter une structure dans ce cas me semble exagéré, d’autant plus qu’il n’ya pas d’ambiguïté ici.

Les tuples sont très utiles dans des langages tels que ML ou Haskell.

En C ++, leur syntaxe les rend moins élégants, mais peut être utile dans les cas suivants:

  • vous avez une fonction qui doit retourner plus d'un argument, mais le résultat est & "local &"; à l'appelant et à l'appelé; vous ne voulez pas définir une structure juste pour cela

  • vous pouvez utiliser la fonction tie pour créer une forme très limitée de correspondance de motif & "a la ML"! ", qui est plus élégante que d'utiliser une structure dans le même but.

  • ils viennent avec prédéfini < opérateurs, ce qui peut vous faire gagner du temps.

J’ai tendance à utiliser des n-uplets en conjonction avec des types de caractères pour atténuer au moins partiellement le problème du «nuple sans nom». Par exemple, si j'avais une structure en grille, alors:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

J'utilise ensuite le type nommé comme suit:

grid_index find(const grid& g, int value);

Ceci est un exemple quelque peu artificiel, mais je pense que la plupart du temps, il trouve un juste milieu entre lisibilité, clarté et facilité d'utilisation.

Ou dans votre exemple:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);

Une des caractéristiques des n-uplets que vous n’avez pas avec les structures est leur initialisation. Considérez quelque chose comme ce qui suit:

struct A
{
  int a;
  int b;
};

Sauf si vous écrivez un make_tuple équivalent ou un constructeur, vous devez d'abord créer un objet temporaire pour utiliser cette structure en tant que paramètre d'entrée:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

Pas mal, cependant, prenons le cas où maintenance ajoute un nouveau membre à notre structure pour une raison quelconque:

struct A
{
  int a;
  int b;
  int c;
};

Les règles d’initialisation des agrégats signifient en fait que notre code continuera à être compilé sans changement. Nous devons donc rechercher toutes les utilisations de cette structure et les mettre à jour, sans aucune aide du compilateur.

Comparez ceci avec un tuple:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

Le compilateur ne peut pas initialiser & "; Tuple &"; avec le résultat de <=>, et génère donc l'erreur qui vous permet de spécifier les valeurs correctes pour le troisième paramètre.

Enfin, l’autre avantage des n-uplets est qu’ils vous permettent d’écrire du code qui itère sur chaque valeur. Ceci n’est tout simplement pas possible avec une structure.

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}

Empêche que votre code soit encombré de nombreuses définitions de structure. C'est plus facile pour la personne qui écrit le code, et pour les autres qui l'utilisent, de documenter chaque élément du tuple au lieu d'écrire votre propre structure / de faire en sorte que les utilisateurs recherchent la définition de la structure.

Il sera plus facile d’écrire les nuplets - inutile de créer une nouvelle structure pour chaque fonction qui retourne quelque chose. La documentation sur ce qui se passe ira à la documentation de la fonction, qui sera de toute façon nécessaire. Pour utiliser la fonction, il faut lire la documentation de la fonction dans tous les cas et le tuple y sera expliqué.

Je suis d'accord avec vous à 100% Roddy.

Pour renvoyer plusieurs valeurs d'une méthode, vous avez plusieurs options autres que les n-uplets. La meilleure option dépend de votre cas:

  1. Création d'une nouvelle structure. Cela est utile lorsque les valeurs multiples que vous renvoyez sont liées , et il est approprié de créer une nouvelle abstraction. Par exemple, je pense & "; Divide_result &"; est une bonne abstraction générale, et le fait de passer cette entité rend votre code beaucoup plus clair que de simplement passer un tuple sans nom. Vous pouvez ensuite créer des méthodes qui fonctionnent sur ce nouveau type, les convertir en d’autres types numériques, etc.

  2. Utilisation de " Out " paramètres. Transmettez plusieurs paramètres par référence et renvoyez plusieurs valeurs en affectant le paramètre chaque sortie. Cela est approprié lorsque votre méthode renvoie plusieurs informations non liées . Dans ce cas, créer une nouvelle structure serait excessif, et avec les paramètres Out, vous insistez sur ce point, plus chaque élément reçoit le nom qu’il mérite.

Les tuples, c'est le mal.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top