2013-06-30 5 views
6

Ci sono dei vantaggi di sovraccaricare un metodo/funzione per prendere il parametro true_type o false_type rispetto all'utilizzo di una dichiarazione if? Vedo sempre più codice utilizzando metodi sovraccaricati con i parametri true_type e false_type.Sovraccarico di una funzione per utilizzare il parametro true_type o false_type rispetto all'utilizzo di un controllo if?

Un breve esempio utilizzando if dichiarazione

void coutResult(bool match) 
{ 
    if (match) 
     cout << "Success." << endl; 
    else 
     cout << "Failure." << endl; 
} 

bool match = my_list.contains(my_value); 
coutResult(match); 

Rispetto ad utilizzando una funzione di sovraccarico:

void coutResult(true_type) 
{ 
    cout << "Success." << endl; 
} 

void coutResult(false_type) 
{ 
    cout << "Failure." << endl; 
} 

bool match = my_list.contains(my_value); 
coutResult(match); 
+1

Ci possono essere dei motivi per farlo nelle funzioni 'constexpr' che sono usate in fase di compilazione. Gli esempi che hai trovato sono stati compilati? (Questo non funzionerebbe con l'output su STDOUT, ovviamente.) – jogojapan

+0

@jogojapan L'ho visto essere usato decine di volte, ma l'ultimo era nella funzione 'find' della libreria stl' algorithm', come visto su [questo video] (http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/Stephan-T-Lavavej-Core-C-7-of-n) alle 15 circa: 00. Sono abbastanza sicuro che sia usato al momento della compilazione. –

risposta

10

Il codice secondo esempio non sarebbe compilare, che è un sintomo della differenza tra la risoluzione di sovraccarico fase di compilazione e fase di esecuzione condizionale ramificazione a "scegliere" quale codice da eseguire.

  • "Il sovraccarico di una funzione di prendere true_type o false_type parametro" consente di fare questa scelta al momento della compilazione se la decisione dipende solo tipi e compilare in tempo costanti.
  • "Uso di un controllo if" è necessaria se la scelta non può essere fatto fino al momento esecuzione quando sono note alcune valori delle variabili.

Nel tuo esempio lo bool match = my_list.contains(my_value) non è ovviamente noto prima di eseguire il programma, quindi non è possibile utilizzare l'overloading.

Ma la differenza è più importante per i modelli , in cui la scelta non è solo "quale percorso da eseguire", ma "quale codice per instantiate e compilare e quindi chiamare". Il codice dal vostro linked video è piuttosto in questo spirito:

Considerate questo codice (sbagliato) (omettendo #include s e std:: s per brevità):

template<typename InIt> 
typename iterator_traits<InIt>::difference_type 
distance(InIt first, InIt last) 
{ 
    // Make code shorter 
    typedef typename iterator_traits<InIt>::difference_type Diff; 
    typedef typename iterator_traits<InIt>::iterator_category Tag; 

    // Choice 
    if (is_same<Tag, random_access_iterator_tag>::value) 
    { 
     return last - first; 
    } 
    else 
    { 
     Diff n = 0; 
     while (first != last) { 
      ++first; 
      ++n; 
     } 
     return n; 
    } 
} 

Ci sono almeno due problemi qui:

  • Se si tenta di chiamare con iteratori che sono non ad accesso casuale (ad esempio std::list<T>::iterator), sarà effettivamente riuscire a compilare (con un errore che indica la linea return last - first;). Il compilatore deve creare un'istanza e compilare l'intero corpo della funzione, tra cui sia il if e le filiali else (anche se deve essere eseguito solo uno) e l'espressione last - first non è valida per gli iteratori non RA.
  • Anche se quel compilato, avremmo fatto un test a fase di esecuzione (con il relativo sovraccarico) per una condizione che avremmo potuto provato appena tempo di compilazione, e la compilazione di parti di codice non necessarie. (Il compilatore può essere in grado di ottimizzare quello, ma questo è il concetto.)

per risolvere il problema che si può fare:

// (assume needed declarations...) 

template<typename InIt> 
typename iterator_traits<InIt>::difference_type 
distance(InIt first, InIt last) 
{ 
    // Make code shorter 
    typedef typename iterator_traits<InIt>::iterator_category Tag; 

    // Choice 
    return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>()); 
} 

template<typename InIt> 
typename iterator_traits<InIt>::difference_type 
distanceImpl(InIt first, InIt last, true_type) 
{ 
    return last - first; 
} 

template<typename InIt> 
typename iterator_traits<InIt>::difference_type 
distanceImpl(InIt first, InIt last, false_type) 
{ 
    // Make code shorter 
    typedef typename iterator_traits<InIt>::difference_type Diff; 

    Diff n = 0; 
    while (first != last) { 
     ++first; 
     ++n; 
    } 
    return n; 
} 

o, in alternativa (possibile qui) con i tipi direttamente:

/* snip */ 
distance(InIt first, InIt last) 
{ 
    /* snip */ 
    return distanceImpl(first, last, Tag()); 
} 

/* snip */ 
distanceImpl(InIt first, InIt last, random_access_iterator_tag) 
{ 
    return last - first; 
} 

/* snip */ 
distanceImpl(InIt first, InIt last, input_iterator_tag) 
{ 
    /* snip */ 
    Diff n = 0; 
    /* snip */ 
    return n; 
} 

Ora solo il "corretto" distanceImpl verrà istanziato e chiamato (la scelta viene eseguita in fase di compilazione).

Questo funziona perché i tipi (ad esempio InIt o Tag) sono noti in fase di compilazione e is_same<Tag, random_access_iterator_tag>::value è una costante che è nota anche in fase di compilazione. Il compilatore può risolvere quale sovraccarico deve essere chiamato, solo in base ai tipi (è la risoluzione di sovraccarico).

Nota: anche se i "tag" vengono passati per valore, vengono utilizzati solo come parametri non utilizzati e non utilizzati per "dispatch" (il loro valore non è utilizzato, solo il loro tipo) e il compilatore può ottimizzarli.

È anche possibile leggere Elemento 47: Utilizzare le classi di tratti per informazioni sui tipi da Scott Meyers Effective C++, Third Edition.

0

In C++, in genere non è possibile derivare in modo esplicito il tipo di oggetto a causa della mancanza di Reflection immobili . Se si desidera operare in base al tipo di oggetto, è necessario un tale tipo di sovraccarico delle funzioni.

Se si desidera utilizzare l'istruzione If, è necessario definire un membro o un metodo aggiuntivo per ciascuna classe correlata per ottenere informazioni sulla classe che potrebbero non essere consentite per alcuni contesti e che introduce un overhead extra del corso. In modo più pratico, è possibile utilizzare typeid anziché questo membro aggiuntivo e l'ID dell'oggetto può essere confrontato con l'id della classe prevista in fase di esecuzione.

Inoltre, se le classi sono abbastanza diverse l'una dall'altra (come non avere una base comune) e sono definite e progettate per scopi completamente separati, è buona prassi progettuale gestirle in metodi separati anziché in un singolo condizionale.

+0

Questa è una buona risposta al perché le funzioni sovraccaricate sono utili. Tuttavia, la domanda originale è il perché sovraccaricare una funzione per due tipi a singola costante (una sorta di sottoinsiemi di 'bool') per sbarazzarsi dell'istruzione' if'. – Inspired

3

Tale sovraccarico è utile per le ottimizzazioni in fase di compilazione. Si noti che nell'esempio si fa riferimento a un typedef viene utilizzato per definire un tipo che corrisponde a std::true_type o std::false_type. Questo tipo viene valutato in fase di compilazione. La creazione di un valore del tipo in una chiamata di funzione successiva è necessaria solo per chiamare la funzione: non è possibile chiamare una funzione con un tipo come argomento.

Non è possibile eseguire il sovraccarico in base a un valore di una variabile . Il sovraccarico viene eseguito in base al tipo.

Il codice

bool match = my_list.contains(my_value); 
coutResult(match); 

non compila in quanto non v'è alcuna coutResult(bool) funzione:

error: no matching function for call to ‘coutResult(bool)’ 
note: candidates are: void coutResult(std::true_type) 
note:     void coutResult(std::false_type) 

Quindi, se l'espressione può essere valutata in fase di compilazione, si può beneficiare di funzioni di sovraccaricare per true_type e false_type rimuovendo così i controlli aggiuntivi in ​​fase di esecuzione. Ma se l'espressione non è una costante, è necessario utilizzare if.