Wäre es sinnvoll, Objekte (anstelle von primitiven Typen) für alles in C ++ zu verwenden?


17

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 PixelCoordinateund PixelSizeKlassen? Oder wäre ich besser dran, wenn ich nur std::pair<int,int>für jedes verwenden würde? Und macht es Sinn, eine ZoomLevelKlasse zu haben , oder sollte ich einfach eine verwenden double?

Meine Intuition, Klassen für alles zu verwenden, basiert auf diesen Annahmen:

  1. Wenn es Klassen für alles gibt, ist es unmöglich, ZoomLevelein WeightObjekt an die Stelle zu übergeben, an der es erwartet wurde. Daher ist es schwieriger, einer Funktion die falschen Argumente zu geben
  2. Ebenso führen einige illegale Vorgänge zu Kompilierungsfehlern, z. B. das Hinzufügen von a GPSCoordinatezu einem ZoomLeveloder einem anderenGPSCoordinate
  3. Rechtsgeschäfte sind einfach darzustellen und typensicher. dh zwei GPSCoordinates 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?


8
"Der meiste C ++ - Code, den ich gesehen habe, verwendet viele primitive Typen" - zwei Gedanken kommen in den Sinn: Ein Großteil des C ++ - Codes wurde möglicherweise von Programmierern geschrieben, die mit C vertraut sind, das eine schwächere Abstraktion aufweist. auch das
Störgesetz

3
Und ich denke, das liegt daran, dass primitive Typen einfach, unkompliziert, schnell, glatt und effizient sind. Im Beispiel des OP ist es definitiv eine gute Idee, einige neue Klassen einzuführen. Aber die Antwort auf die Titelfrage (wo "alles" steht) ist ein klares Nein!
Mr Lister

1
@Max typedeffing das Doppelte tut absolut nichts, um das Problem des OP zu lösen.
Mr Lister

1
@CongXu: Ich hatte fast den gleichen Gedanken, aber mehr im Sinne von "Viel Code in jeder Sprache wurde möglicherweise von Programmierern geschrieben, die Probleme mit der Erstellung von Abstraktionen hatten".
Doc Brown

1
Wie das Sprichwort sagt "Wenn Sie eine Funktion mit 17 Parametern haben, haben Sie wahrscheinlich ein paar verpasst".
Joris Timmermans

Antworten:


24

Ja auf jeden Fall. Funktionen / Methoden, die zu viele Argumente annehmen, sind ein Codegeruch und weisen auf mindestens eine der folgenden Eigenschaften hin:

  1. Die Funktion / Methode macht zu viele Dinge auf einmal
  2. Die Funktion / Methode erfordert Zugriff auf so viele Dinge, weil sie fragt, kein OO-Designgesetz sagt oder verletzt
  3. Die Argumente hängen tatsächlich eng zusammen

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:

static GPSCoordinate getGPS(Plane plane, Angle3D gimbalAngle, GPSView view)

(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 PixelCoordinatevor std::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 a ScreenCoordinatean a PixelCoordinate, obwohl beide pairs sind.


1
ganz zu schweigen davon, dass Sie die größeren Strukturen als Referenz für das kleine bisschen Mikrooptimierung weitergeben können, das all die coolen Kids versuchen, aber eigentlich nicht tun sollten
Ratschenfreak

6

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 Heightund IdKlassen).

Wenn es Klassen für alles gibt, ist es unmöglich, eine ZoomLevel zu übergeben, in der ein Weight-Objekt erwartet wurde, sodass es schwieriger ist, einer Funktion die falschen Argumente zuzuweisen

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.

Ebenso können einige illegale Vorgänge zu Kompilierungsfehlern führen, z. B. das Hinzufügen einer GPS-Koordinate zu einem ZoomLevel oder einer anderen GPS-Koordinate

Gute Nutzung von Bedienerüberladungen.

Rechtsgeschäfte sind einfach darzustellen und typensicher. Das heißt, das Subtrahieren von zwei GPS-Koordinaten würde eine GPS-Verschiebung ergeben

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.


3

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 PlaneAngle3dund eine separate Klasse GimbalAngle3d, 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 Verwendung floatgenauso gut wie doubleaber speicher- und CPU-effizienter sein könnte, müssen Sie dies nur an einer Stelle ändern und nicht in Dutzenden.


+1 für den Typedef-Vorschlag. Holen Sie sich das Beste aus beiden Welten: primitive Typen und Objekte.
Karl Bielefeldt

Es ist nicht schwieriger, den Typ eines Klassenmitglieds zu ändern als einen Typedef. oder meintest du im vergleich zu primitiven?
MaHuJa

2

Hier gibt es bereits gute Antworten, aber eines möchte ich hinzufügen:

Mit PixelCoordinateund PixelSizeclass werden die Dinge sehr spezifisch. Ein General Coordinateoder eine Point2DKlasse passt wahrscheinlich besser zu Ihren Bedürfnissen. A Point2Dsollte eine Klasse sein, die die häufigsten Punkt- / Vektoroperationen implementiert. Sie können eine solche Klasse sogar generisch implementieren ( Point2D<T>wobei T intoder doubleoder 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.


1
Sie können auch tun, struct PixelCoordinate:Point2D<int>wenn Sie die richtige Art von Sicherheit wollen
Ratschenfreak

0

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.

Es 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").

Oder wäre ich besser dran, wenn ich nur std :: pair für jedes verwenden würde.

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.

Und macht es Sinn, eine ZoomLevel-Klasse zu haben, oder sollte ich einfach ein double verwenden?

Sie könnten es auch tippen, aber ich würde eine verwenden, doublebis ich die dazugehörige Funktionalität benötige, die es für ein Double nicht gibt (zum Beispiel ZoomLevel::defaultwürde ein Wert erfordern, dass Sie eine Klasse definieren, aber bis Sie etwas Ähnliches haben dass, halte es ein Doppel).

Meine Intuition hinter der Verwendung von Klassen für alles basiert auf diesen Annahmen [der Kürze halber weggelassen]

Es handelt sich um starke Argumente (Sie sollten sich immer darum bemühen, dass Ihre Benutzeroberflächen leicht und falsch zu bedienen sind).

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.

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.

Ist es eine gute Idee, Objekte für irgendetwas zu verwenden, oder hat es Nachteile, die ich nicht kenne?

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 coordinateKlasse zu haben , als überall drei Parameter zu übertragen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.