Während eines Projekts, an dem ich kürzlich gearbeitet habe, musste ich viele Funktionen verwenden, die so aussehen:
static bool getGPS(double plane_latitude, double plane_longitude, double plane_altitude,
double plane_roll, double plane_pitch, double plane_heading,
double gimbal_roll, double gimbal_pitch, double gimbal_yaw,
int target_x, int target_y, double zoom,
int image_width_pixels, int image_height_pixels,
double & Target_Latitude, double & Target_Longitude, double & Target_Height);
Ich möchte es so umgestalten, dass es ungefähr so aussieht:
static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)
Dies scheint mir deutlich lesbarer und sicherer zu sein als die erste Methode. Aber macht es Sinn, zu erstellen PixelCoordinate
und PixelSize
Klassen? Oder wäre ich besser dran, wenn ich nur std::pair<int,int>
für jedes verwenden würde? Und macht es Sinn, eine ZoomLevel
Klasse zu haben , oder sollte ich einfach eine verwenden double
?
Meine Intuition, Klassen für alles zu verwenden, basiert auf diesen Annahmen:
- Wenn es Klassen für alles gibt, ist es unmöglich,
ZoomLevel
einWeight
Objekt an die Stelle zu übergeben, an der es erwartet wurde. Daher ist es schwieriger, einer Funktion die falschen Argumente zu geben - Ebenso führen einige illegale Vorgänge zu Kompilierungsfehlern, z. B. das Hinzufügen von a
GPSCoordinate
zu einemZoomLevel
oder einem anderenGPSCoordinate
- Rechtsgeschäfte sind einfach darzustellen und typensicher. dh zwei
GPSCoordinate
s zu subtrahieren, würde a ergebenGPSDisplacement
Der meiste C ++ - Code, den ich gesehen habe, verwendet jedoch viele primitive Typen, und ich stelle mir vor, dass es dafür einen guten Grund geben muss. Ist es eine gute Idee, Objekte für irgendetwas zu verwenden, oder hat es Nachteile, die ich nicht kenne?
Antworten:
Ja auf jeden Fall. Funktionen / Methoden, die zu viele Argumente annehmen, sind ein Codegeruch und weisen auf mindestens eine der folgenden Eigenschaften hin:
Wenn der letzte Fall der Fall ist (und Ihr Beispiel legt es mit Sicherheit nahe), ist es höchste Zeit, etwas von der "Abstraktion" zu machen , von der die coolen Kids vor einigen Jahrzehnten gesprochen haben. In der Tat würde ich über Ihr Beispiel hinausgehen und etwas tun wie:
(Ich bin nicht mit GPS vertraut, daher ist dies wahrscheinlich keine korrekte Abstraktion. Beispielsweise verstehe ich nicht, was Zoom mit einer aufgerufenen Funktion zu tun hat
getGPS
.)Bitte ziehen Sie Klassen wie
PixelCoordinate
vorstd::pair<T, T>
, auch wenn beide dieselben Daten haben. Es erhöht den semantischen Wert und erhöht die Typensicherheit (der Compiler verhindert die Übergabe von aScreenCoordinate
an aPixelCoordinate
, obwohl beidepair
s sind.quelle
Haftungsausschluss: Ich bevorzuge C gegenüber C ++
Anstelle von Klassen würde ich Strukturen verwenden. Dieser Punkt ist bestenfalls pedantisch, da Strukturen und Klassen nahezu identisch sind (siehe hier für ein einfaches Diagramm oder diese SO- Frage), aber ich denke, dass die Unterscheidung von Nutzen ist.
C-Programmierer kennen Strukturen als Datenblöcke, die eine größere Bedeutung haben, wenn sie gruppiert werden. Wenn ich eine Klasse sehe, gehe ich davon aus, dass es interne Zustände und Methoden gibt, um diesen Zustand zu manipulieren. Eine GPS-Koordinate hat keinen Status, nur Daten.
Aus Ihrer Frage geht hervor, dass dies genau das ist, wonach Sie suchen, ein allgemeiner Container, keine zustandsbehaftete Struktur. Wie die anderen bereits erwähnt haben, würde ich Zoom nicht in eine eigene Klasse einordnen. Ich persönlich hasse Klassen gebaut , um einen Datentyp zu halten (meine Professoren lieben die Schaffung
Height
undId
Klassen).Ich denke nicht, dass dies ein Problem ist. Wenn Sie die Anzahl der Parameter reduzieren, indem Sie die offensichtlichen Dinge so gruppieren, wie Sie es getan haben, sollten die Header ausreichen, um diese Probleme zu vermeiden. Wenn Sie wirklich besorgt sind, trennen Sie die Primitive mit einer Struktur, die Ihnen Kompilierungsfehler für häufige Fehler geben sollte. Es ist einfach, zwei Parameter nebeneinander auszutauschen, aber schwieriger, wenn sie weiter voneinander entfernt sind.
Gute Nutzung von Bedienerüberladungen.
Auch gute Nutzung von Bedienerüberladungen.
Ich denke, Sie sind auf dem richtigen Weg. Eine andere zu berücksichtigende Sache ist, wie dies in den Rest des Programms passt.
quelle
Die Verwendung dedizierter Typen hat den Vorteil, dass es viel schwieriger ist, die Reihenfolge der Argumente zu ändern. Die erste Version Ihrer Beispielfunktion beginnt beispielsweise mit einer Kette von 9 Doppelwerten. Wenn jemand diese Funktion nutzt, ist es sehr einfach, die Reihenfolge zu verfälschen und die Kardanwinkel anstelle der GPS-Koordinaten zu übergeben und umgekehrt. Dies kann zu sehr seltsamen und schwer auffindbaren Fehlern führen. Wenn Sie stattdessen nur drei Argumente mit unterschiedlichen Typen übergeben, kann dieser Fehler vom Compiler abgefangen werden.
Ich würde in der Tat überlegen, noch einen Schritt weiter zu gehen und Typen einzuführen, die technisch identisch sind, aber eine andere semantische Bedeutung haben. Zum Beispiel durch eine Klasse
PlaneAngle3d
und eine separate KlasseGimbalAngle3d
, obwohl beide eine Sammlung von drei Doppelklassen sind. Dies würde es unmöglich machen, eines als das andere zu verwenden, es sei denn, es ist beabsichtigt.Ich würde die Verwendung von typedefs empfehlen, die nur Aliase für primitive types (
typedef double ZoomLevel
) sind, nicht nur aus diesem Grund, sondern auch für einen anderen: Sie können den Typ später problemlos ändern. Wenn Sie eines Tages die Idee haben, dass die Verwendungfloat
genauso gut wiedouble
aber speicher- und CPU-effizienter sein könnte, müssen Sie dies nur an einer Stelle ändern und nicht in Dutzenden.quelle
Hier gibt es bereits gute Antworten, aber eines möchte ich hinzufügen:
Mit
PixelCoordinate
undPixelSize
class werden die Dinge sehr spezifisch. Ein GeneralCoordinate
oder einePoint2D
Klasse passt wahrscheinlich besser zu Ihren Bedürfnissen. APoint2D
sollte eine Klasse sein, die die häufigsten Punkt- / Vektoroperationen implementiert. Sie können eine solche Klasse sogar generisch implementieren (Point2D<T>
wobei Tint
oderdouble
oder etwas anderes sein kann).2D-Punkt- / Vektoroperationen sind für viele 2D-Geometrieprogrammierungsaufgaben so häufig, dass eine solche Entscheidung meiner Erfahrung nach die Wahrscheinlichkeit einer Wiederverwendung vorhandener 2D-Operationen in hohem Maße erhöht. Sie werden die Notwendigkeit einer erneuten Durchführung der sie für jeden vermeiden
PixelCoordinate
,GPSCoordinate
„whats“ -Koordinate Klasse immer wieder.quelle
struct PixelCoordinate:Point2D<int>
wenn Sie die richtige Art von Sicherheit wollenEs ist [lesbarer]. Das ist auch eine gute Idee.
Aber ist es sinnvoll, PixelCoordinate- und PixelSize-Klassen zu erstellen?
Wenn sie unterschiedliche Konzepte darstellen, ist dies sinnvoll (dh "ja").
Sie könnten damit anfangen
typedef std::pair<int,int> PixelCoordinate, PixelSize;
. Auf diese Weise decken Sie die Semantik ab (Sie arbeiten mit Pixelkoordinaten und Pixelgrößen, nicht mit std :: pairs in Ihrem Client-Code), und wenn Sie sie erweitern müssen, ersetzen Sie einfach die typedef durch eine Klasse, und Ihr Client-Code sollte dies tun erfordern minimale Änderungen.Sie könnten es auch tippen, aber ich würde eine verwenden,
double
bis ich die dazugehörige Funktionalität benötige, die es für ein Double nicht gibt (zum BeispielZoomLevel::default
würde ein Wert erfordern, dass Sie eine Klasse definieren, aber bis Sie etwas Ähnliches haben dass, halte es ein Doppel).Es handelt sich um starke Argumente (Sie sollten sich immer darum bemühen, dass Ihre Benutzeroberflächen leicht und falsch zu bedienen sind).
Es gibt Gründe; In vielen Fällen sind diese Gründe jedoch nicht gut. Ein triftiger Grund dafür ist möglicherweise die Leistung. Dies bedeutet jedoch, dass Sie diese Art von Code als Ausnahme und nicht in der Regel sehen sollten.
Es ist im Allgemeinen eine gute Idee, sicherzustellen, dass, wenn Ihre Werte immer zusammen angezeigt werden (und keinen Sinn ergeben), Sie sie zusammen gruppieren sollten (aus den gleichen Gründen, die Sie in Ihrem Beitrag erwähnt haben).
Wenn Sie also Koordinaten haben, die immer x, y und z haben, ist es viel besser, eine
coordinate
Klasse zu haben , als überall drei Parameter zu übertragen.quelle