Avrebbe senso usare gli oggetti (invece dei tipi primitivi) per tutto in C ++?

17

Durante un recente progetto a cui ho lavorato, ho dovuto utilizzare molte funzioni che assomigliano a questo:

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);

Quindi voglio refactoring per assomigliare a questo tipo:

static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
                            PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)

Questo mi sembra molto più leggibile e sicuro del primo metodo. Ma ha senso creare PixelCoordinate e PixelSize classi? O starei meglio usando solo std::pair<int,int> per ciascuno. E ha senso avere una classe ZoomLevel o dovrei semplicemente usare un double ?

La mia intuizione dietro l'uso delle classi per tutto è basata su questi presupposti:

  1. Se ci sono classi per tutto, sarebbe impossibile passare un ZoomLevel in cui era previsto un oggetto Weight , quindi sarebbe più difficile fornire gli argomenti sbagliati a una funzione
  2. Analogamente, alcune operazioni illegali potrebbero causare errori di compilazione, come aggiungere GPSCoordinate a ZoomLevel o un altro GPSCoordinate
  3. Le operazioni legali saranno facili da rappresentare e dattiloscritte. cioè sottraendo due% diGPSCoordinate s si otterrebbe un GPSDisplacement

Tuttavia, la maggior parte del codice C ++ che ho visto utilizza molti tipi primitivi, e immagino ci debba essere una buona ragione per quello. È una buona idea usare gli oggetti per qualsiasi cosa, o ha aspetti negativi di cui non sono a conoscenza?

    
posta zackg 28.03.2013 - 08:47
fonte

5 risposte

24

Sì, sicuramente. Funzioni / metodi che richiedono troppi argomenti è un odore di codice e indica a almeno uno dei seguenti:

  1. La funzione / metodo è fare troppe cose contemporaneamente
  2. La funzione / metodo richiede l'accesso a molte cose perché è chiedere, non dire o violare alcune Legge di design OO
  3. Gli argomenti sono in realtà strettamente correlati

Se l'ultimo è il caso (e il tuo esempio lo suggerisce di certo), è giunto il momento di fare alcuni di quei pantaloni di fantasia " astrazione " di cui i ragazzi fantastici stavano parlando, oh, solo alcuni decenni fa. In realtà, andrei oltre il tuo esempio e fare qualcosa del tipo:

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

(Non ho familiarità con il GPS, quindi probabilmente non è un'astrazione corretta, ad esempio, non capisco quale zoom abbia a che fare con una funzione chiamata getGPS .)

Prego preferire classi come PixelCoordinate su std::pair<T, T> , anche se i due hanno gli stessi dati. Aggiunge valore semantico, oltre a un po 'di sicurezza di tipo extra (il compilatore ti impedirà di passare un ScreenCoordinate a un PixelCoordinate anche se entrambi sono pair s.

    
risposta data 28.03.2013 - 09:16
fonte
6

Dichiarazione di non responsabilità: preferisco C a C ++

Invece delle classi, userei le strutture. Questo punto è pedante al meglio, dal momento che le strutture e le classi sono quasi identiche (vedi qui per un semplice grafico, o questo SO domanda), ma penso ci sia un merito nella differenziazione.

I programmatori C hanno familiarità con le strutture come blocchi di dati che hanno un significato maggiore quando sono raggruppati, mentre quando vedo un clasas, presumo che ci siano alcuni stati e metodi interni per manipolare quello stato. Una coordinata GPS non ha stato, solo dati.

Dalla tua domanda, sembra che questo sia esattamente quello che stai cercando, un contenitore generale, non una struttura statica. Come gli altri hanno menzionato, non mi preoccuperei di mettere Zoom in una classe a parte; Personalmente odio le classi costruite per contenere un tipo di dati (i miei professori adorano creare Height e Id classi).

If there are classes for everything, it would be impossible to pass a ZoomLevel in where a Weight object was expected, so it would be more difficult to provide the wrong arguments to a function

Non penso che questo sia un problema Se riduci il numero di parametri raggruppando le cose ovvie come hai fatto, le intestazioni dovrebbero essere sufficienti per prevenire questi problemi. Se sei davvero preoccupato, separa i primati con una struct, che dovrebbe darti errori di compilazione per errori comuni. È facile scambiare due parametri uno accanto all'altro, ma più difficile se sono più distanti.

Likewise, some illegal operations would cause compile errors, such as adding a GPSCoordinate to a ZoomLevel or another GPSCoordinate

Buon uso dei sovraccarichi dell'operatore.

Legal operations will be easy to represent and typesafe. i.e subtracting two GPSCoordinates would yield a GPSDisplacement

Buon uso anche dei sovraccarichi dell'operatore.

Penso che tu sia sulla strada giusta. Un'altra cosa da considerare è come questo si inserisce nel resto del programma.

    
risposta data 28.03.2013 - 13:14
fonte
3

L'uso di tipi dedicati ha il vantaggio che è molto più difficile rovinare l'ordine degli argomenti. La prima versione della funzione di esempio, ad esempio, inizia con una catena di 9 doppi. Quando qualcuno usa questa funzione, è molto facile rovinare l'ordine e passare gli angoli cardanici al posto delle coordinate GPS e viceversa. Questo può portare a bug molto strani e difficili da rintracciare. Quando passi solo tre argomenti con tipi diversi, questo errore può essere catturato dal compilatore.

In effetti, considererei un ulteriore passo avanti e introdurrei tipi tecnicamente identici ma con un significato semantico diverso. Ad esempio avendo una classe PlaneAngle3d e una classe separata GimbalAngle3d , anche se entrambi sono una raccolta di tre doppi. Ciò renderebbe impossibile l'utilizzo di uno come l'altro, a meno che non sia intenzionale.

Suggerirei di usare typedef che sono solo alias per i tipi primitivi ( typedef double ZoomLevel ) non solo per questo motivo, ma anche per un altro: consente facilmente di cambiare il tipo in un secondo momento. Un giorno ti viene in mente che usare float potrebbe essere buono quanto double ma potrebbe essere più efficiente in termini di memoria e CPU, devi solo cambiarlo in un posto, e non in dozends.

    
risposta data 28.03.2013 - 10:10
fonte
2

Risposte giuste qui già, ma c'è una cosa che vorrei aggiungere:

L'utilizzo della classe PixelCoordinate e PixelSize rende le cose molto specifiche. Una classe generale Coordinate o Point2D potrebbe probabilmente adattarsi meglio alle tue esigenze. Un Point2D dovrebbe essere una classe che implementa le operazioni point / vector più comuni. Potresti implementare una classe di questo tipo anche genericamente ( Point2D<T> dove T potrebbe essere int o double o qualcos'altro).

Le operazioni 2D punto / vettore sono così comuni per molte attività di programmazione della geometria 2D che alla mia esperienza tale decisione aumenterà notevolmente la possibilità di riutilizzare le operazioni 2D esistenti. Eviterai la necessità di reimplementarli per ogni PixelCoordinate , GPSCoordinate , "Whatsover" -Coordinate di nuovo ancora e ancora.

    
risposta data 28.03.2013 - 13:37
fonte
0

So I want to refactor it to look something like this:

static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
                        PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)

This appears to me to be significantly more readable and safe than the first method.

È [più leggibile]. È anche una buona idea.

Ma ha senso creare classi PixelCoordinate e PixelSize?

Se rappresentano concetti diversi, ha senso (cioè "sì").

Or would I be better off just using std::pair for each.

Potresti iniziare facendo typedef std::pair<int,int> PixelCoordinate, PixelSize; . In questo modo, tu copri la semantica (stai lavorando con le coordinate pixel e le dimensioni dei pixel, non con std :: pairs nel tuo codice cliente) e se hai bisogno di estenderlo, devi semplicemente sostituire typedef con una classe e il tuo codice client dovrebbe richiedono modifiche minime.

And does it make sense to have a ZoomLevel class, or should I just use a double?

Puoi anche digitare typedef, ma userei double fino a quando non avrò bisogno di una funzionalità ad esso associata che non esiste per un doppio (ad esempio, se hai un valore ZoomLevel::default richiederebbe di definire una classe, ma finché non hai qualcosa del genere, tienilo un doppio).

My intuition behind using classes for everything is based on these assumptions [omitted for brevity]

Sono argomenti forti (dovresti sempre cercare di rendere le tue interfacce facili da usare correttamente e difficili da usare in modo errato).

However, most C++ code I've seen uses a lot of primitive types, and I imagine there must be a good reason for that.

Ci sono ragioni; in molti casi però, queste ragioni non sono buone. Un motivo valido per farlo potrebbe essere la prestazione, ma ciò significa che dovresti vedere questo tipo di codice come eccezione, non come regola.

Is it a good idea to use objects for anything, or does it have downsides that I am not aware of?

Generalmente è una buona idea assicurarsi che se i tuoi valori appaiono sempre insieme (e non hanno senso a parte) devi raggrupparli insieme (per gli stessi motivi che hai menzionato nel tuo post).

Cioè, se tu con le coordinate che hanno sempre x, yez, allora è molto meglio avere una classe coordinate di trasmettere tre parametri ovunque.

    
risposta data 28.03.2013 - 12:21
fonte

Leggi altre domande sui tag