Domanda

Ho una classe che incapsula alcuni calcoli aritmetici, diciamo calcoli in virgola fissa.Mi piace l'idea di sovraccaricare gli operatori aritmetici, quindi scrivo quanto segue:

class CFixed
{
   CFixed( int   );
   CFixed( float );
};

CFixed operator* ( const CFixed& a, const CFixed& b )
{ ... }

Funziona tutto.Posso scrivere 3 * CFixed(0) e CFixed(3) * 10.0f.Ma ora mi rendo conto che posso implementare operator* con un operando intero in modo molto più efficace.Quindi lo sovraccarico:

CFixed operator* ( const CFixed& a, int b )
{ ... }
CFixed operator* ( int a, const CFixed& b )
{ ... }

Funziona ancora, ma ora CFixed(0) * 10.0f chiama la versione sovraccaricata, convertendo float in int (e mi aspettavo che convertisse float in CFixed).Naturalmente, posso sovraccaricare anche una versione float, ma mi sembra un'esplosione combinatoria di codice.Esiste una soluzione alternativa (o sto progettando la mia classe in modo errato)?Come posso dire al compilatore di chiamare la versione sovraccaricata dell'operatore* SOLO con int?

È stato utile?

Soluzione

Supponendo che desideri scegliere la versione specializzata per qualsiasi tipo integrale (e non solo int in particolare, una cosa che potresti fare è fornirla come funzione modello e usare Boost.EnableIf per rimuovere tali sovraccarichi dal set di sovraccarichi disponibile, se l'operando non è un tipo integrale.

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>

class CFixed
{
public:
   CFixed( int   ) {}
   CFixed( float ) {}
};

CFixed operator* ( const CFixed& a, const CFixed&  )
{ puts("General CFixed * CFixed"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( const CFixed& a, T  )
{ puts("CFixed * [integer type]"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( T , const CFixed& b )
{ puts("[integer type] * CFixed"); return b; }


int main()
{
    CFixed(0) * 10.0f;
    5 * CFixed(20.4f);
    3.2f * CFixed(10);
    CFixed(1) * 100u;
}

Naturalmente, potresti anche utilizzare una condizione diversa per rendere disponibili tali sovraccarichi solo se T = int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

Per quanto riguarda la progettazione della classe, forse potresti affidarti di più ai modelli. Ad esempio, il costruttore potrebbe essere un modello e, di nuovo, se dovessi distinguere tra tipi integrali e reali, dovrebbe essere possibile utilizzare questa tecnica.

Altri suggerimenti

Dovresti sovraccaricarlo float anche digitare.Conversione da int al tipo specificato dall'utente (CFixed) ha una priorità inferiore rispetto alla conversione integrale mobile incorporata float.Quindi il compilatore sceglierà sempre la funzione con int, a meno che non si aggiunga la funzione con float anche.

Per maggiori dettagli, leggere la sezione 13.3 dello standard C++03.Sentire il dolore.

Sembra che anch'io ne abbia perso le tracce.:-( Zio Bens segnala che l'aggiunta del solo float non risolve il problema, poiché la versione con double andrebbe aggiunto anche questo.Ma in ogni caso aggiungere diversi operatori relativi ai tipi incorporati è noioso, ma non comporta un incremento combinatorio.

Se hai costruttori che possono essere invocati con un solo argomento, hai effettivamente creato un operatore di conversione implicito. Nel tuo esempio, ovunque sia necessario un CFixed, è possibile passare sia un int che un float. Questo è ovviamente pericoloso, perché il compilatore potrebbe generare silenziosamente codice che chiama la funzione sbagliata invece di abbaiarti quando ti sei dimenticato di includere una dichiarazione di qualche funzione.

Quindi una buona regola empirica dice che, ogni volta che scrivi costruttori che possono essere chiamati con un solo argomento (nota che questo foo(int i, bool b = false) può essere chiamato anche con un argomento, anche se ci vogliono due argomenti) , dovresti creare quel costruttore explicit, a meno che non desideri davvero avviare la conversione implicita. std::string::string(const char*) I costruttori non vengono utilizzati dal compilatore per conversioni implicite.

Dovresti cambiare la tua classe in questo:

class CFixed
{
   explicit CFixed( int   );
   explicit CFixed( float );
};

Ho scoperto che ci sono pochissime eccezioni a questa regola. (<=> è piuttosto famoso.)

Modifica: Mi dispiace, ho perso il punto di non consentire conversioni implicite da <=> a <=>.

L'unico modo in cui vedo impedirlo è fornire anche agli operatori <=>.

Che ne dici di rendere esplicita la conversione esplicito ?

?

D'accordo con sbi, dovresti assolutamente rendere espliciti i costruttori a parametro singolo.

È possibile evitare un'esplosione nell'operatore < > funzioni che scrivi con i template, comunque:

template <class T>
CFixed operator* ( const CFixed& a, T b ) 
{ ... } 

template <class T>
CFixed operator* ( T a, const CFixed& b ) 
{ ... } 

A seconda del codice presente nelle funzioni, questo verrà compilato solo con i tipi da cui supporti la conversione.

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