Namespace + berfungsi versus metode statis di kelas

290

Katakanlah saya memiliki, atau akan menulis, serangkaian fungsi terkait. Katakanlah itu terkait matematika. Secara organisasi, saya harus:

  1. Tulis fungsi-fungsi ini dan letakkan di my MyMath namespace dan lihat melaluiMyMath::XYZ()
  2. Buat kelas yang dipanggil MyMathdan buat metode ini statis dan lihat yang serupaMyMath::XYZ()

Mengapa saya memilih salah satu dari yang lain sebagai cara mengatur perangkat lunak saya?

RobertL
sumber
4
untuk satu hal, ruang nama adalah tambahan yang lebih baru untuk bahasa, dibandingkan dengan kelas dan metode statis, yang ada dalam bahasa sejak saat itu disebut "C dengan kelas". Beberapa programmer mungkin lebih nyaman dengan fitur yang lebih lama. Beberapa programmer lain mungkin menggunakan kompiler lama. Hanya $ 0,02 saya
Rom
21
@ ROM: Anda benar tentang "programmer lama", tetapi salah tentang "kompiler lama". Ruang nama dikompilasi dengan benar sejak ribuan tahun (saya bekerja dengannya dengan Visual C ++ 6, yang berasal dari tahun 1998!). Adapun "C dengan kelas", beberapa orang di forum ini bahkan tidak dilahirkan ketika itu terjadi: Menggunakan ini sebagai argumen untuk menghindari fitur C ++ standar dan tersebar luas adalah sebuah kekeliruan. Kesimpulannya, hanya kompiler C ++ yang usang tidak mendukung ruang nama. Jangan gunakan argumen itu sebagai alasan untuk tidak menggunakannya.
paercebal
@paercebal: beberapa kompiler kuno masih digunakan di dunia tertanam. Tidak mendukung ruang nama mungkin merupakan salah satu ketidaknyamanan terkecil yang harus dilakukan ketika menulis kode untuk berbagai CPU kecil yang setiap orang berinteraksi dengan setiap hari: stereo, microwave, unit kontrol mesin di mobil Anda, lampu lalu lintas, dll. Hanya untuk menjadi jelas: Saya tidak menganjurkan untuk tidak menggunakan kompiler yang lebih baik, lebih baru di mana-mana. Au conrare: Saya semua untuk fitur bahasa terbaru (kecuali RTTI;)). Saya hanya menunjukkan bahwa kecenderungan seperti itu ada
Rom
13
@Rom: Dalam kasus saat ini, penulis pertanyaan memiliki pilihan, jadi ternyata, tidak ada kompiler yang gagal untuk mengkompilasi kode namespaced. Dan karena ini adalah pertanyaan tentang C ++, jawaban C ++ harus diberikan, termasuk menyebutkan ruang nama dan solusi RTTI untuk masalah jika diperlukan. Memberikan jawaban C, atau jawaban C-with-class-for-usang-kompiler keluar dari topik.
paercebal
2
"Hanya $ 0,02 saya" - akan lebih bernilai jika Anda telah memberikan bukti kompiler C ++ yang masih ada yang tidak mendukung ruang nama. "beberapa kompiler kuno masih digunakan di dunia tertanam" - ini konyol; "dunia yang disematkan" adalah perkembangan yang lebih baru daripada ruang nama C ++. "C dengan kelas" diperkenalkan pada tahun 1979, jauh sebelum ada yang menanamkan kode C dalam apa pun. "Saya hanya menunjukkan bahwa kecenderungan seperti itu ada" - meskipun begitu, itu tidak ada hubungannya dengan pertanyaan ini.
Jim Balter

Jawaban:

243

Secara default, gunakan fungsi namespaced.

Kelas adalah untuk membangun objek, bukan untuk mengganti ruang nama.

Dalam kode Berorientasi Objek

Scott Meyers menulis seluruh Item untuk buku C ++ Efektifnya tentang topik ini, "Lebih suka fungsi bukan teman yang bukan anggota daripada fungsi anggota". Saya menemukan referensi online untuk prinsip ini dalam sebuah artikel dari Herb Sutter:http://www.gotw.ca/gotw/084.htm

Yang penting untuk diketahui adalah bahwa: Dalam fungsi C ++ di namespace yang sama dengan kelas milik antarmuka kelas itu (karena ADL akan mencari fungsi-fungsi itu ketika menyelesaikan panggilan fungsi).

Fungsi dengan nama tempat, kecuali dinyatakan sebagai "teman," tidak memiliki akses ke internal kelas, sedangkan metode statis memiliki.

Ini berarti, misalnya, bahwa ketika mempertahankan kelas Anda, jika Anda perlu mengubah internal kelas Anda, Anda perlu mencari efek samping dalam semua metodenya, termasuk yang statis.

Ekstensi I

Menambahkan kode ke antarmuka kelas.

Di C #, Anda bisa menambahkan metode ke kelas bahkan jika Anda tidak memiliki akses ke sana. Tetapi dalam C ++, ini tidak mungkin.

Tapi, masih di C ++, Anda masih bisa menambahkan fungsi namespaced, bahkan ke kelas yang dituliskan untuk Anda.

Lihat dari sisi lain, ini penting ketika mendesain kode Anda, karena dengan meletakkan fungsi Anda di namespace, Anda akan mengotorisasi pengguna Anda untuk menambah / melengkapi antarmuka kelas.

Ekstensi II

Efek samping dari poin sebelumnya, tidak mungkin untuk mendeklarasikan metode statis dalam banyak header. Setiap metode harus dideklarasikan di kelas yang sama.

Untuk ruang nama, fungsi dari namespace yang sama dapat dideklarasikan dalam banyak header (fungsi swap yang hampir standar adalah contoh terbaik dari itu).

Perpanjangan III

Kesejukan dasar namespace adalah bahwa dalam beberapa kode, Anda dapat menghindari menyebutkannya, jika Anda menggunakan kata kunci "menggunakan":

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Dan Anda bahkan dapat membatasi "polusi" ke satu kelas:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

"Pola" ini wajib untuk penggunaan yang tepat dari idiom swap yang hampir standar.

Dan ini tidak mungkin dilakukan dengan metode statis di kelas.

Jadi, namespace C ++ memiliki semantik sendiri.

Tapi itu lebih jauh, karena Anda dapat menggabungkan ruang nama dengan cara yang mirip dengan warisan.

Misalnya, jika Anda memiliki namespace A dengan fungsi AAA, namespace B dengan fungsi BBB, Anda dapat mendeklarasikan namespace C, dan membawa AAA dan BBB di namespace ini dengan menggunakan kata kunci.

Kesimpulan

Ruang nama adalah untuk ruang nama. Kelas adalah untuk kelas.

C ++ dirancang sehingga setiap konsep berbeda, dan digunakan secara berbeda, dalam kasus yang berbeda, sebagai solusi untuk masalah yang berbeda.

Jangan gunakan kelas saat Anda membutuhkan ruang nama.

Dan dalam kasus Anda, Anda perlu ruang nama.

paercebal
sumber
Bisakah jawaban ini diterapkan ke utas juga, yaitu apakah lebih baik menggunakan ruang nama daripada metode statis untuk utas?
cepat
3
@dashesy: ruang nama vs metode statis tidak ada hubungannya dengan utas, jadi ya, ruang nama lebih baik karena ruang nama hampir selalu lebih baik daripada metode statis. Jika satu hal, metode statis memiliki akses ke variabel anggota kelas, sehingga mereka entah bagaimana memiliki nilai enkapsulasi lebih rendah daripada ruang nama. Dan mengisolasi data bahkan lebih penting dalam eksekusi berulir.
paercebal
@ paercebal- terima kasih, saya menggunakan metode kelas statis untuk fungsi thread. Sekarang saya mengerti bahwa saya menyalahgunakan kelas sebagai namespace, jadi menurut Anda apa pendekatan terbaik untuk memiliki banyak utas dalam satu objek? Saya telah mengajukan pertanyaan ini pada SO juga, saya menghargai jika Anda dapat menjelaskan (di sini atau dalam pertanyaan itu sendiri)
dashesy
1
@dashesy: ​​Anda meminta masalah. Apa yang Anda inginkan dengan utas berbeda adalah mengisolasi data yang tidak seharusnya dibagikan, sehingga memiliki beberapa utas yang memiliki akses istimewa ke data pribadi suatu kelas adalah ide yang buruk. Saya akan menyembunyikan satu utas di dalam kelas, dan pastikan untuk mengisolasi data untuk utas itu dari data untuk utas utama. Tentu saja, data yang seharusnya dibagikan kemudian dapat menjadi anggota kelas itu, tetapi mereka masih harus disinkronkan (kunci, atom, dll.). Saya tidak yakin tentang berapa banyak lib yang dapat Anda akses, tetapi menggunakan tugas / async lebih baik.
paercebal
jawaban paercebal haruslah jawaban yang diterima! Hanya satu tautan lagi untuk swap yang hampir standar () melalui namespace + ADL -> stackoverflow.com/questions/6380862/…
Gob00st
54

Ada banyak orang yang tidak setuju dengan saya, tetapi ini adalah bagaimana saya melihatnya:

Kelas pada dasarnya adalah definisi dari jenis objek tertentu. Metode statis harus mendefinisikan operasi yang terkait erat dengan definisi objek.

Jika Anda hanya akan memiliki sekelompok fungsi terkait yang tidak terkait dengan objek yang mendasari atau definisi jenis objek , maka saya akan mengatakan pergi dengan namespace saja. Bagi saya, secara konseptual, ini jauh lebih masuk akal.

Misalnya, dalam kasus Anda, tanyakan pada diri sendiri, "Apa itu MyMath?" Jika MyMathtidak mendefinisikan jenis objek, maka saya akan mengatakan: jangan membuatnya menjadi kelas.

Tapi seperti yang saya katakan, saya tahu ada banyak orang yang (bahkan dengan keras) tidak setuju dengan saya dalam hal ini (khususnya, pengembang Java dan C #).

Dan Tao
sumber
3
Anda memiliki perspektif yang sangat murni tentang ini. Tapi secara praktis, sebuah kelas dengan metode semua-statis bisa berguna: Anda dapat typedefmenggunakannya, menggunakannya sebagai parameter templat, dll.
Shog9
56
Itu karena Jave dan C # orang tidak punya pilihan.
Martin York
7
@ shog9. Anda dapat menyesuaikan fungsi juga!
Martin York
6
@ Dan: mungkin, salah satu yang membutuhkan rutinitas matematika dan ingin mendukung "memasukkan" implementasi yang berbeda.
Shog9
1
@ Dan: "Saya pikir jika seseorang tertarik menggunakan kelas sebagai parameter templat, kelas itu hampir pasti mendefinisikan beberapa objek yang mendasarinya." Tidak, tidak sama sekali. Pikirkan ciri-ciri. (Meskipun demikian, saya sepenuhnya setuju dengan jawaban Anda.)
sbi
18
  • Jika Anda memerlukan data statis, gunakan metode statis.
  • Jika itu adalah fungsi templat dan Anda ingin dapat menentukan satu set parameter templat untuk semua fungsi secara bersamaan, maka gunakan metode statis dalam kelas templat.

Kalau tidak, gunakan fungsi namespace.


Menanggapi komentar: ya, metode statis dan data statis cenderung terlalu banyak digunakan. Itu sebabnya saya hanya menawarkan dua skenario terkait yang menurut saya bisa membantu. Dalam contoh spesifik OP (satu set rutin matematika), jika ia ingin kemampuan untuk menentukan parameter - katakanlah, tipe data inti dan presisi keluaran - yang akan diterapkan pada semua rutinitas, ia mungkin melakukan sesuatu seperti:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Jika Anda tidak membutuhkannya, maka dengan segala cara menggunakan namespace.

Shog9
sumber
2
yang disebut data statis dapat data tingkat namespace dalam file implementasi namespace, ini mengurangi kopling lebih karena tidak harus muncul di header.
Motti
Data statis tidak lebih baik dari namespace-scope global.
coppro
@coppro Mereka setidaknya satu langkah meningkatkan rantai evolusi dari global acak karena mereka dapat dijadikan pribadi (tetapi sebaliknya setuju).
Martin York
@Motti: OTOH, jika Anda menginginkannya di header (fungsi inline / template), Anda kembali menjadi jelek tentangnya.
Shog9
1
Contoh menarik, menggunakan kelas sebagai steno untuk menghindari pengulangan templateargumen!
underscore_d
13

Anda harus menggunakan namespace, karena namespace memiliki banyak keunggulan dibandingkan kelas:

  • Anda tidak harus mendefinisikan semuanya di header yang sama
  • Anda tidak perlu memaparkan semua implementasi Anda di header
  • Anda tidak dapat usingmenjadi anggota kelas; kamu bisausing menjadi anggota namespace
  • Anda tidak bisa using class, meskipunusing namespace tidak selalu merupakan ide yang bagus
  • Menggunakan kelas menyiratkan bahwa ada beberapa objek yang akan dibuat ketika sebenarnya tidak ada

Anggota statis, menurut saya, sangat sering digunakan. Mereka bukanlah kebutuhan nyata dalam banyak kasus. Fungsi anggota statis mungkin lebih baik sebagai fungsi lingkup file, dan anggota data statis hanyalah objek global dengan reputasi yang lebih baik dan tidak layak.

coppro
sumber
3
"Anda tidak perlu memaparkan semua implementasi Anda di header" tidak Anda lakukan ketika Anda menggunakan kelas.
Vanuan
Bahkan lebih lagi: Jika Anda menggunakan ruang nama, Anda tidak dapat mengekspos semua implementasi Anda di header (Anda akan berakhir dengan beberapa definisi simbol). Fungsi anggota kelas sebaris memungkinkan Anda untuk melakukan itu.
Vanuan
1
@Vanuan: Anda dapat mengekspos implementasi namespace di header. Cukup gunakan inlinekata kunci untuk memenuhi ODR.
Thomas Eding
@ Thomas tidak perlu! = Bisa
Vanuan
3
@ Vanuan: Hanya satu hal yang dijamin oleh kompiler saat menggunakan inline, dan BUKAN "inlining" tubuh fungsi. Tujuan nyata (dan dijamin oleh standar) inlineadalah untuk mencegah berbagai definisi. Baca tentang "Aturan Satu Definisi" untuk C ++. Selain itu, pertanyaan SO yang ditautkan tidak dikompilasi karena masalah header yang sudah dikompilasi, bukan masalah ODR.
Thomas Eding
3

Saya lebih suka namespace, dengan cara itu Anda dapat memiliki data pribadi dalam namespace anonim dalam file implementasi (jadi itu tidak harus muncul di header sama sekali sebagai lawan privateanggota). Manfaat lain adalah bahwa dengan usingnamespace Anda, klien metode dapat memilih untuk tidak menentukanMyMath::

Motti
sumber
2
Anda dapat memiliki data pribadi di namespace anonim di file implementasi dengan kelas juga. Tidak yakin saya mengikuti logika Anda.
Patrick Johnmeyer
0

Satu lagi alasan untuk menggunakan kelas - Opsi untuk memanfaatkan penentu akses. Anda kemudian dapat memecah metode statis publik Anda menjadi metode pribadi yang lebih kecil. Metode publik dapat memanggil beberapa metode pribadi.

Milind T
sumber
6
Pengubah akses itu keren, tetapi bahkan sebagian besar privatemetode lebih mudah diakses daripada metode yang prototipenya tidak diterbitkan sama sekali di header (dan dengan demikian, tetap tidak terlihat). Saya bahkan tidak menyebutkan enkapsulasi yang lebih baik yang ditawarkan oleh fungsi namespace anonim.
paercebal
2
Metode pribadi, IMO, lebih rendah daripada menyembunyikan fungsi itu sendiri dalam implementasi (file cpp) dan tidak pernah memaparkannya dalam file header. Tolong uraikan hal ini dalam jawaban Anda dan mengapa Anda lebih suka menggunakan anggota pribadi . Sampai saat itu -1.
nonsensickle
@nonsensickle Mungkin maksudnya fungsi mammoth dengan banyak bagian berulang dapat dipecah dengan aman sambil menyembunyikan subbagian yang menyinggung di balik private, menghentikan orang lain dari mendapatkan mereka jika mereka berbahaya / perlu penggunaan yang sangat hati-hati.
Troyseph
1
@Troyseph demikian, Anda dapat menyembunyikan informasi ini di dalam namespace yang tidak disebutkan namanya dalam .cppfile yang akan membuatnya pribadi untuk unit terjemahan tanpa memberikan informasi berlebihan kepada siapa pun yang membaca file header. Secara efektif, saya mencoba mengadvokasi untuk idiom PIMPL.
nonsensickle
Anda tidak bisa memasukkannya ke dalam .cppfile jika Anda ingin menggunakan templat.
Yay295
0

Baik namespace dan metode kelas memiliki kegunaannya. Namespace memiliki kemampuan untuk disebarkan ke seluruh file, namun itu adalah kelemahan jika Anda perlu menegakkan semua kode terkait untuk masuk dalam satu file. Seperti disebutkan di atas, kelas juga memungkinkan Anda membuat anggota statis privat di kelas. Anda dapat memilikinya di namespace anonim dari file implementasi namun masih lingkup yang lebih besar daripada memilikinya di dalam kelas.

rocd
sumber
"[menyimpan sesuatu] dalam ruang nama anonim dari file implementasi [adalah] ruang lingkup yang lebih besar daripada memiliki mereka di dalam kelas" - tidak, tidak. dalam kasus-kasus di mana akses istimewa untuk anggota tidak diperlukan, hal-hal yang diberi nama secara anonim lebih pribadi daripada yang dilakukan private:orang. dan dalam banyak kasus di mana akses istimewa tampaknya diperlukan, itu dapat diperhitungkan. fungsi paling 'pribadi' adalah yang tidak muncul di header. private:metode tidak pernah dapat menikmati manfaat ini.
underscore_d