Apakah masuk akal untuk menggunakan objek (bukan tipe primitif) untuk semua yang ada di C ++?

17

Selama proyek baru-baru ini yang saya kerjakan, saya harus menggunakan banyak fungsi yang terlihat seperti ini:

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

Jadi saya ingin mengubahnya agar terlihat seperti ini:

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

Bagi saya ini tampaknya jauh lebih mudah dibaca dan aman daripada metode pertama. Tetapi apakah masuk akal untuk membuat PixelCoordinatedan PixelSizekelas? Atau lebih baik saya menggunakan std::pair<int,int>masing-masing saja. Dan apakah masuk akal untuk memiliki ZoomLevelkelas, atau haruskah saya menggunakan saja double?

Intuisi saya di balik menggunakan kelas untuk semuanya didasarkan pada asumsi ini:

  1. Jika ada kelas untuk semuanya, tidak mungkin untuk lulus ZoomLeveldi mana Weightobjek diharapkan, sehingga akan lebih sulit untuk memberikan argumen yang salah ke suatu fungsi
  2. Demikian juga, beberapa operasi ilegal akan menyebabkan kesalahan kompilasi, seperti menambahkan a GPSCoordinateke yang ZoomLevellainGPSCoordinate
  3. Operasi legal akan mudah untuk diwakili dan typesafe. yaitu mengurangi dua GPSCoordinates akan menghasilkan aGPSDisplacement

Namun, sebagian besar kode C ++ yang saya lihat menggunakan banyak tipe primitif, dan saya membayangkan pasti ada alasan bagus untuk itu. Apakah ide yang bagus untuk menggunakan objek untuk apa pun, atau apakah ada kelemahan yang tidak saya sadari?

zackg
sumber
8
"sebagian besar kode C ++ yang saya lihat menggunakan banyak tipe primitif" - dua pemikiran muncul di benak: banyak kode C ++ mungkin telah ditulis oleh programmer yang mengenal C yang memiliki abstraksi yang lebih lemah; juga Hukum Sturgeon
congusbongus
3
Dan saya pikir itu karena tipe primitif sederhana, mudah, cepat, ramping dan efisien. Sekarang dalam contoh OP, itu ide yang bagus untuk memperkenalkan beberapa kelas baru. Tetapi jawaban untuk pertanyaan judul (di mana dikatakan "semuanya") adalah tidak!
Tn. Lister
1
@ Max mengetikkan ganda tidak melakukan apa-apa untuk menyelesaikan masalah OP.
Tn. Lister
1
@CongXu: Saya memiliki pemikiran yang hampir sama, tetapi lebih dalam arti "banyak kode dalam bahasa apa pun mungkin telah ditulis oleh programmer yang mengalami masalah dengan membangun abstraksi sama sekali".
Doc Brown
1
Seperti kata pepatah "jika Anda memiliki fungsi dengan 17 parameter, Anda mungkin melewatkan beberapa".
Joris Timmermans

Jawaban:

24

Iya tentu saja. Fungsi / metode yang terlalu banyak argumen adalah bau kode , dan menunjukkan setidaknya salah satu dari yang berikut:

  1. Fungsi / metode melakukan terlalu banyak hal sekaligus
  2. Fungsi / metode memerlukan akses ke banyak hal karena meminta, tidak memberi tahu atau melanggar beberapa undang-undang desain OO
  3. Argumen ini sebenarnya terkait erat

Jika yang terakhir adalah kasusnya (dan contoh Anda tentu saja menyarankan demikian), inilah saatnya untuk melakukan "abstraksi" celana mewah yang dibicarakan anak-anak keren itu, beberapa dekade yang lalu. Bahkan, saya akan melangkah lebih jauh dari contoh Anda dan melakukan sesuatu seperti:

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

(Saya tidak terbiasa dengan GPS jadi ini mungkin bukan abstraksi yang benar; misalnya, saya tidak mengerti apa yang harus dilakukan zoom dengan fungsi yang disebut getGPS.)

Tolong lebih suka kelas seperti PixelCoordinatelebih std::pair<T, T>, bahkan jika keduanya memiliki data yang sama. Ini menambah nilai semantik, ditambah sedikit keamanan tipe tambahan (kompiler akan menghentikan Anda dari meneruskan ScreenCoordinateke PixelCoordinatemeskipun keduanya pairs.

congusbongus
sumber
1
belum lagi Anda dapat melewati struct yang lebih besar dengan referensi untuk itu sedikit optimasi mikro semua anak keren coba lakukan tetapi benar-benar tidak boleh
ratchet freak
6

Penafian: Saya lebih suka C ke C ++

Alih-alih kelas, saya akan menggunakan struct. Poin ini sangat bagus, karena struct dan kelas hampir identik (lihat di sini untuk grafik sederhana, atau pertanyaan SO ini ), tapi saya pikir ada kelebihan dalam diferensiasi.

Pemrogram C akrab dengan struct sebagai blok data yang memiliki makna lebih besar ketika dikelompokkan, sedangkan ketika saya melihat clasas, saya berasumsi ada beberapa keadaan internal dan metode untuk memanipulasi keadaan itu. Koordinat GPS tidak memiliki status, hanya data.

Dari pertanyaan Anda, tampaknya inilah yang sebenarnya Anda cari, wadah umum, bukan struktur stateful. Seperti yang disebutkan orang lain, saya tidak akan repot menempatkan Zoom ke kelasnya sendiri; Saya pribadi benci kelas yang dibangun untuk menampung tipe data (profesor saya suka membuat Heightdan Idkelas).

Jika ada kelas untuk semuanya, tidak mungkin untuk melewatkan ZoomLevel di mana objek Bobot diharapkan, jadi akan lebih sulit untuk memberikan argumen yang salah ke suatu fungsi

Saya rasa ini bukan masalah. Jika Anda mengurangi jumlah parameter dengan mengelompokkan hal-hal yang jelas telah Anda lakukan, tajuk seharusnya cukup untuk mencegah masalah tersebut. Jika Anda benar-benar khawatir, pisahkan primitif dengan struct, yang seharusnya memberi Anda kompilasi kesalahan untuk kesalahan umum. Sangat mudah untuk menukar dua parameter di sebelah satu sama lain, tetapi lebih sulit jika mereka terpisah lebih jauh.

Demikian juga, beberapa operasi ilegal akan menyebabkan kesalahan kompilasi, seperti menambahkan GPSCoordinate ke ZoomLevel atau GPSCoordinate lainnya

Penggunaan berlebihan operator yang baik.

Operasi legal akan mudah untuk diwakili dan typesafe. yaitu mengurangi dua GPSCoordinate akan menghasilkan GPSDisplacement

Juga penggunaan berlebihan operator.

Saya pikir Anda berada di jalur yang benar. Hal lain yang perlu dipertimbangkan adalah bagaimana ini sesuai dengan sisa program.

hajar
sumber
3

Menggunakan tipe khusus memiliki keuntungan bahwa jauh lebih sulit untuk mengacaukan urutan argumen. Versi pertama dari fungsi contoh Anda, misalnya, dimulai dengan rantai 9 ganda. Ketika seseorang menggunakan fungsi ini, sangat mudah untuk mengacaukan pesanan dan melewatkan sudut gimbal di tempat koordinat GPS dan sebaliknya. Ini dapat menyebabkan bug yang sangat aneh dan sulit dilacak. Saat Anda hanya memberikan tiga argumen dengan tipe yang berbeda, kesalahan ini dapat ditangkap oleh kompiler.

Saya sebenarnya akan mempertimbangkan untuk melangkah lebih jauh dan memperkenalkan jenis-jenis yang secara teknis identik tetapi memiliki makna semantik yang berbeda. Misalnya dengan memiliki kelas PlaneAngle3ddan kelas yang terpisah GimbalAngle3d, meskipun keduanya merupakan kumpulan dari tiga ganda. Ini tidak memungkinkan untuk menggunakan yang satu sebagai yang lain, kecuali disengaja.

Saya akan merekomendasikan menggunakan typedef yang hanya alias untuk tipe primitif ( typedef double ZoomLevel) tidak hanya karena alasan itu, tetapi juga untuk yang lain: Mudah memungkinkan Anda untuk mengubah jenisnya nanti. Ketika Anda mendapatkan ide suatu hari bahwa menggunakan floatbisa sama baiknya doubletetapi bisa lebih hemat memori dan CPU, Anda hanya perlu mengubahnya di satu tempat, dan bukan di dozend.

Philipp
sumber
+1 untuk saran typedef. Memberi Anda yang terbaik dari kedua dunia: tipe dan objek primitif.
Karl Bielefeldt
Tidaklah sulit untuk mengubah tipe anggota kelas daripada typedef; atau maksud Anda dibandingkan dengan primitif?
MaHuJa
2

Sudah ada jawaban bagus di sini, tetapi ada satu hal yang ingin saya tambahkan:

Menggunakan PixelCoordinatedan PixelSizekelas membuat hal-hal menjadi sangat spesifik. Seorang jenderal Coordinateatau Point2Dkelas mungkin lebih sesuai dengan kebutuhan Anda. A Point2Dharus berupa kelas yang mengimplementasikan operasi titik / vektor paling umum. Anda bisa mengimplementasikan kelas semacam itu bahkan secara umum (di Point2D<T>mana T mungkinint atau doubleatau sesuatu yang lain).

Operasi titik / vektor 2D sangat umum untuk banyak tugas pemrograman geometri 2D sehingga menurut pengalaman saya keputusan seperti itu akan meningkatkan peluang penggunaan kembali operasi 2D yang ada. Anda akan menghindari kebutuhan untuk-menerapkan kembali mereka untuk setiap PixelCoordinate, GPSCoordinate, "whatsover" kelas -Coordinate lagi dan lagi.

Doc Brown
sumber
1
Anda juga dapat melakukannya struct PixelCoordinate:Point2D<int>jika Anda ingin jenis keamanan yang tepat
ratchet freak
0

Jadi saya ingin mengubahnya agar terlihat seperti ini:

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

Bagi saya ini tampaknya jauh lebih mudah dibaca dan aman daripada metode pertama.

Ini [lebih mudah dibaca]. Itu juga ide yang bagus.

Tetapi apakah masuk akal untuk membuat kelas PixelCoordinate dan PixelSize?

Jika mereka mewakili konsep yang berbeda, itu masuk akal (yaitu, "ya.").

Atau lebih baik saya menggunakan std :: pair untuk masing-masing.

Anda bisa mulai dengan melakukan typedef std::pair<int,int> PixelCoordinate, PixelSize;. Dengan cara ini, Anda menutupi semantik (Anda bekerja dengan koordinat piksel dan ukuran piksel, bukan dengan std :: pasang dalam kode klien Anda) dan jika Anda perlu memperluas, Anda cukup mengganti typedef dengan kelas dan kode klien Anda harus membutuhkan perubahan minimal.

Dan apakah masuk akal untuk memiliki kelas ZoomLevel, atau haruskah saya menggunakan ganda?

Anda bisa mengetiknya juga, tapi saya akan menggunakan doublesampai saya membutuhkan fungsionalitas yang terkait dengannya yang tidak ada untuk double (misalnya, memiliki ZoomLevel::defaultnilai akan mengharuskan Anda mendefinisikan kelas, tetapi sampai Anda memiliki sesuatu seperti itu, pertahankan dua kali lipat).

Intuisi saya di balik menggunakan kelas untuk semuanya didasarkan pada asumsi ini [dihilangkan untuk singkatnya]

Mereka adalah argumen yang kuat (Anda harus selalu berusaha untuk membuat antarmuka Anda mudah digunakan dengan benar dan sulit digunakan secara tidak benar).

Namun, sebagian besar kode C ++ yang saya lihat menggunakan banyak tipe primitif, dan saya membayangkan pasti ada alasan bagus untuk itu.

Ada alasannya; dalam banyak kasus, alasan itu tidak baik. Salah satu alasan yang valid untuk melakukan ini mungkin kinerja, tetapi itu berarti bahwa Anda harus melihat kode semacam ini sebagai pengecualian, bukan sebagai aturan.

Apakah ide yang bagus untuk menggunakan objek untuk apa pun, atau apakah ada kelemahan yang tidak saya sadari?

Secara umum adalah ide yang baik untuk memastikan bahwa jika nilai Anda selalu muncul bersama (dan tidak masuk akal), Anda harus mengelompokkannya (untuk alasan yang sama seperti yang Anda sebutkan dalam posting Anda).

Artinya, jika Anda dengan koordinat yang selalu memiliki x, y, dan z, maka jauh lebih baik memiliki coordinatekelas daripada mengirimkan tiga parameter di mana-mana.

utnapistim
sumber