Frage

Ich habe eine Klasse kapselt einige arithmetische, lassen Sie uns sagen, fixed point Berechnungen.Ich mag die Idee, überladen von arithmetischen Operatoren, so dass ich das folgende schreiben:

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

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

Es funktioniert alles.Ich kann schreiben 3 * CFixed(0) und CFixed(3) * 10.0 f.Aber jetzt begreife ich, kann ich implementieren operator* mit einem integer-Operanden viel effektiver.Also ich überlasten es:

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

Es funktioniert immer noch, aber jetzt CFixed(0) * 10.0 f ruft überladene version, der Umwandlung von float zu int ( und ich es erwartet konvertieren von float-zu CFixed ).Natürlich kann ich eine überlastung float Versionen, aber es scheint eine kombinatorische explosion der code für mich.Gibt es eine Problemumgehung (oder bin ich die Gestaltung meiner Klasse falsch)?Wie kann ich dem compiler zu nennen überladene version von operator* NUR mit ints?

War es hilfreich?

Lösung

Angenommen, Sie die spezielle Version für jeden integralen Typ, abgeholt werden möchten (und nicht nur int insbesondere eine Sache, ist, dass als Template-Funktion bereitstellen tun könnte und verwenden Boost.EnableIf diese Überlastungen des verfügbaren Überlastung Satz zu entfernen, wenn der Operand nicht integraler Typ ist.

#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;
}

Natürlich können Sie auch eine andere Bedingung verwenden, um diese Überlastungen nur, wenn T = int zur Verfügung zu stellen: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

In Bezug auf die Klasse entwerfen, vielleicht könnten Sie auf Vorlagen mehr verlassen. Z. B, könnte der Konstruktor eine Vorlage sein, und wieder, sollten Sie zwischen Integral- und echten Typen unterscheiden müssen, soll es möglich sein, diese Technik zu verwenden.

Andere Tipps

Sie sollten auch mit float Typ überlasten. Umwandlung von int zu vom Benutzer angegebenen Typ (CFixed) eine niedrigere Priorität als Einbau-Floating-integral Umwandlung in float. So wird der Compiler immer wählen Funktion mit int, es sei denn, Sie Funktion mit float als auch hinzufügen.

Für weitere Details lesen 13.3 Abschnitt von C ++ 03-Standard. Spüren Sie den Schmerz.

Es scheint, dass ich den Überblick über sie zu verloren haben. :-( UncleBens berichtet, dass Schwimmer Hinzufügen nur das Problem nicht lösen, als Version mit double ebenfalls hinzugefügt werden soll. aber in jedem Fall des Hinzufügen mehrerer Betreiber im Zusammenhang mit eingebauten Typen ist mühsam, aber führt nicht zu einem kombinatorischen Schub.

Wenn Sie Konstrukteure haben, die mit nur einem Argument aufgerufen werden kann, erstellt man effektiv eine implizite Konvertierung Operator. In Ihrem Beispiel, wo ein CFixed benötigt wird, sowohl ein int und ein float geführt werden können. Das ist natürlich gefährlich, weil der Compiler leise Code generieren könnte die falsche Funktion statt zu bellen bei Ihnen anrufen, wenn Sie einige Funktion Erklärung vergessen aufzunehmen.

Daher ist eine gute Daumenregel besagt, dass, wann immer Sie Konstrukteure schreiben, welcher sich mit nur einem Argument aufgerufen werden können (beachten Sie, dass dies ein foo(int i, bool b = false) kann mit einem Argument aufgerufen werden, auch, obwohl es zwei Argumente übernimmt), Sie machen, dass Konstruktor explicit sollte, es sei denn, Sie wirklich implizite Konvertierung wollen in treten. explicit Konstruktoren werden nicht durch den Compiler für implizite Konvertierungen verwendet.

Sie müßten Ihre Klasse, dies ändern:

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

Ich habe festgestellt, dass es nur sehr wenige Ausnahmen von dieser Regel. (std::string::string(const char*) ist ein ziemlich berühmter.)

Edit: Es tut mir leid, ich verpasst den Punkt über nicht implizite Konvertierungen von int float ermöglicht.

Der einzige Weg, ich sehe dies zu verhindern ist es, die Betreiber für float auch zur Verfügung zu stellen.

Wie wäre die Umwandlung machen explizit ?

Einverstanden mit sbi, sollten Sie auf jeden Fall machen Sie Ihre single-parameter Konstruktoren explizit.

Sie können vermeiden, eine explosion in der operator<> die Funktionen, die Sie schreiben-mit Vorlagen, jedoch:

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

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

Je nachdem, auf welchen code der Funktionen, diese werden nur kompilieren mit Typen, die Sie unterstützen, konvertieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top