Domanda

Sto cercando di aggirare le tuple (grazie a @litb) e il suggerimento comune per il loro uso è per le funzioni che ritornano > 1 valore

Questo è qualcosa per cui normalmente userei una struttura, e in questo caso non riesco a capire i vantaggi delle tuple: sembra un approccio soggetto a errori per i terminali pigri.

Prendendo in prestito un esempio , lo userei

struct divide_result {
    int quotient;
    int remainder;
};

Usando una tupla, avresti

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

Ma senza leggere il codice della funzione che stai chiamando (o i commenti, se sei abbastanza stupido da fidarti di loro) non hai idea di quale int sia il quoziente e viceversa. Sembra piuttosto ...

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

... che non mi riempirebbe di fiducia.

Quindi, quali sono i vantaggi delle tuple rispetto alle strutture che compensano l'ambiguità?

È stato utile?

Soluzione

tuple

Penso di essere d'accordo con te sul fatto che il problema con quale posizione corrisponde a quale variabile può creare confusione. Ma penso che ci siano due lati. Uno è il call-side e l'altro è il call-side :

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

Penso che sia chiaro ciò che abbiamo ottenuto, ma può diventare confuso se devi restituire più valori contemporaneamente. Una volta che il programmatore del chiamante ha cercato la documentazione di div, saprà quale posizione è cosa e potrà scrivere un codice efficace. Come regola generale, direi di non restituire più di 4 valori contemporaneamente. Per qualsiasi cosa oltre, preferisci una struttura.

parametri di output

Naturalmente è possibile utilizzare anche i parametri di output:

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

Ora penso che illustri come le tuple siano migliori dei parametri di output. Abbiamo mescolato l'input di operator>> con il suo output, senza ottenere alcun vantaggio. Peggio ancora, lasciamo in dubbio il lettore di quel codice su quale potrebbe essere il valore di ritorno effettivo di tie essere. ci sono meravigliosi esempi in cui i parametri di output sono utili. Secondo me, dovresti usarli solo quando non hai altro modo, perché il valore di ritorno è già preso e non può essere cambiato in una tupla o in una struttura. <=> è un buon esempio di dove usi i parametri di output, perché il valore di ritorno è già riservato allo stream, quindi puoi concatenare <=> chiamate. Se non hai a che fare con gli operatori e il contesto non è cristallino, ti consiglio di utilizzare i puntatori, per segnalare sul lato della chiamata che l'oggetto viene effettivamente utilizzato come parametro di output, oltre ai commenti ove appropriato.

restituzione di una struttura

La terza opzione è usare una struttura:

div_result d = div(10, 3);

Penso che vince sicuramente il premio per chiarezza . Ma nota che devi ancora accedere al risultato all'interno di quella struttura, e il risultato non è & Quot; messo a nudo & Quot; sulla tabella, come nel caso dei parametri di output e della tupla utilizzata con <=>.

Penso che un punto importante in questi giorni sia rendere tutto il più generico possibile. Quindi, supponiamo che tu abbia una funzione in grado di stampare le tuple. Puoi semplicemente

cout << div(10, 3);

E visualizza il tuo risultato. Penso che le tuple, dall'altro lato, vincano chiaramente per la loro natura versatile . Per fare ciò con div_result, è necessario sovraccaricare l'operatore & Lt; & Lt ;, oppure è necessario emettere ciascun membro separatamente.

Altri suggerimenti

Un'altra opzione è usare una mappa di Boost Fusion (codice non testato):

struct quotient;
struct remainder;

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

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

Puoi accedere ai risultati in modo relativamente intuitivo:

using boost::fusion::at_key;

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

Ci sono anche altri vantaggi, come la possibilità di iterare sui campi della mappa, ecc. ecc. Vedi doco per ulteriori informazioni.

Con le tuple, puoi usare tie, che a volte è abbastanza utile: std::tr1::tie (quotient, remainder) = do_division ();. Questo non è così facile con le strutture. In secondo luogo, quando si utilizza il codice modello, a volte è più semplice fare affidamento sulle coppie piuttosto che aggiungere un altro typedef per il tipo di struttura.

E se i tipi sono diversi, allora una coppia / tupla non è in realtà peggio di una struttura. Pensa ad esempio a pair<int, bool> readFromFile(), dove int è il numero di byte letti e bool è se l'eof è stato colpito. Aggiungere una struttura in questo caso mi sembra eccessivo, soprattutto perché qui non c'è ambiguità.

Le tuple sono molto utili in linguaggi come ML o Haskell.

In C ++, la loro sintassi li rende meno eleganti, ma può essere utile nelle seguenti situazioni:

  • hai una funzione che deve restituire più di un argomento, ma il risultato è " local " al chiamante e alla chiamata; non vuoi definire una struttura solo per questo

  • puoi usare la funzione tie per fare una forma molto limitata di pattern matching " a la " ;, che è più elegante che usare una struttura per lo stesso scopo.

  • vengono forniti con < predefinito; operatori, che possono far risparmiare tempo.

Tendo a usare le tuple insieme ai typedef per alleviare almeno parzialmente il problema della "tupla senza nome". Ad esempio, se avessi una struttura a griglia, allora:

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

Quindi uso il tipo denominato come:

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

Questo è un esempio un po 'inventato, ma penso che la maggior parte delle volte colpisca un mezzo felice tra leggibilità, esplicitazione e facilità d'uso.

O nel tuo esempio:

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

Una caratteristica delle tuple che non hai con le strutture è nella loro inizializzazione. Considera qualcosa di simile al seguente:

struct A
{
  int a;
  int b;
};

A meno che tu non scriva un make_tuple equivalente o costruttore, quindi per utilizzare questa struttura come parametro di input devi prima creare un oggetto temporaneo:

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

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

Non male, tuttavia, prendi il caso in cui la manutenzione aggiunga un nuovo membro alla nostra struttura per qualsiasi motivo:

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

Le regole di inizializzazione aggregata significano che il nostro codice continuerà a essere compilato senza modifiche. Dobbiamo quindi cercare tutti gli usi di questa struttura e aggiornarli, senza alcun aiuto da parte del compilatore.

Contrasta questo con una tupla:

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
}

Il compilatore non può initailizzare " Tuple " con il risultato di <=> e genera quindi l'errore che consente di specificare i valori corretti per il terzo parametro.

Infine, l'altro vantaggio delle tuple è che ti permettono di scrivere codice che scorre su ogni valore. Questo semplicemente non è possibile usando una struttura.

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

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

Impedisce che il codice sia disseminato di molte definizioni di struttura. È più facile per la persona che scrive il codice e per altri che lo usano quando si documenta semplicemente quale sia ciascun elemento nella tupla, piuttosto che scrivere la propria struttura / indurre le persone a cercare la definizione della struttura.

Le tuple saranno più facili da scrivere - non è necessario creare una nuova struttura per ogni funzione che restituisce qualcosa. La documentazione su ciò che va dove andrà alla documentazione della funzione, che sarà comunque necessaria. Per utilizzare la funzione è necessario leggere la documentazione della funzione in ogni caso e la tupla verrà spiegata lì.

Sono d'accordo con te Roddy al 100%.

Per restituire più valori da un metodo, hai diverse opzioni oltre alle tuple, la migliore delle quali dipende dal tuo caso:

  1. Creazione di una nuova struttura. Questo è utile quando i valori multipli che stai restituendo sono correlati ed è appropriato creare una nuova astrazione. Ad esempio, penso & Quot; divide_result & Quot; è una buona astrazione generale e il passaggio di questa entità rende il codice molto più chiaro del semplice passaggio di una tupla senza nome. È quindi possibile creare metodi che funzionino con questo nuovo tipo, convertirlo in altri tipi numerici, ecc.

  2. Uso di " Out " parametri. Passare più parametri per riferimento e restituire più valori assegnando a ciascun parametro out. Ciò è appropriato quando il metodo restituisce diverse informazioni non correlate . La creazione di una nuova struttura in questo caso sarebbe eccessiva e con i parametri Out si enfatizza questo punto, inoltre ogni elemento ottiene il nome che merita.

Le tuple sono cattive.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top