Apakah kelas utilitas dengan anggota statis merupakan anti-pola dalam C ++?


39

Pertanyaan Di mana saya harus meletakkan fungsi-fungsi yang tidak terkait dengan suatu kelas telah memicu beberapa perdebatan tentang apakah masuk akal dalam C ++ untuk menggabungkan fungsi-fungsi utilitas dalam suatu kelas atau hanya membuatnya ada sebagai fungsi bebas dalam namespace.

Saya berasal dari latar belakang C # di mana opsi terakhir tidak ada dan dengan demikian cenderung menggunakan kelas statis dalam kode C ++ kecil yang saya tulis. Namun jawaban tertinggi pada pertanyaan itu serta beberapa komentar mengatakan bahwa fungsi bebas lebih disukai, bahkan menunjukkan kelas statis adalah anti-pola. Mengapa begitu di C ++? Setidaknya di permukaan, metode statis di kelas tampaknya tidak bisa dibedakan dari fungsi bebas di namespace. Mengapa demikian preferensi untuk yang terakhir?

Apakah ada yang berbeda, jika koleksi fungsi utilitas memerlukan beberapa data bersama, mis. Cache yang dapat disimpan dalam bidang statis pribadi?


Kedengarannya agak seperti antipattern "dekomposisi fungsional".
user281377

7
Jawaban singkat: Anda tidak perlu kelas untuk membungkus fungsi-fungsi ini. Fungsi bebas lebih cocok untuk tugas Anda daripada mengolahnya menjadi beberapa pseudo-OO construct, yang merupakan solusi yang hanya Anda perlukan dalam bahasa "murni-OO".
Chris mengatakan Reinstate Monica

Jawaban:


39

Saya kira untuk menjawab bahwa kita harus membandingkan niat dari kedua kelas dan ruang nama. Menurut Wikipedia:

Kelas

Dalam pemrograman berorientasi objek, kelas adalah konstruksi yang digunakan sebagai cetak biru untuk membuat instance itu sendiri - disebut sebagai instance kelas, objek kelas, objek contoh atau objek sederhana. Kelas mendefinisikan anggota konstituen yang memungkinkan instance kelas ini memiliki status dan perilaku. Anggota bidang data (variabel anggota atau variabel instan) memungkinkan objek kelas untuk mempertahankan status. Jenis anggota lain, terutama metode, memungkinkan perilaku objek kelas. Kelas contoh adalah dari jenis kelas terkait.

Namespace

Secara umum, namespace adalah wadah yang menyediakan konteks untuk pengidentifikasi (nama, atau istilah teknis, atau kata-kata) yang dimilikinya, dan memungkinkan disambiguasi pengidentifikasi homonim yang berada di ruang nama yang berbeda.

Sekarang, apa yang ingin Anda capai dengan meletakkan fungsi di kelas (statis) atau namespace? Saya berani bertaruh bahwa definisi namespace lebih baik menggambarkan niat Anda - semua yang Anda inginkan adalah wadah untuk fungsi Anda. Anda tidak memerlukan fitur yang dijelaskan dalam definisi kelas. Perhatikan bahwa kata-kata pertama dari definisi kelas adalah " Dalam pemrograman berorientasi objek ", namun tidak ada yang berorientasi objek tentang kumpulan fungsi.

Mungkin ada alasan teknis juga, tetapi sebagai seseorang yang datang dari Jawa dan mencoba memahami bahasa multi-paradigma yaitu C ++, jawaban yang paling jelas bagi saya adalah: Karena kita tidak perlu OO untuk mencapai ini.


1
+1 untuk "kita tidak perlu OO untuk mencapai ini"
Ixrec

Saya setuju dengan ini sebagai bukan penggemar OO, tapi saya kira ada beberapa situasi di mana menggunakan kelas All-statis adalah satu-satunya solusi, misalnya, mengganti namespace, karena Anda tidak dapat mendeklarasikan namespace bersarang di dalam kelas.
Wael Boutglay

26

Saya akan sangat berhati-hati dalam menyebut itu sebagai anti-pola. Namespaces biasanya lebih disukai, tetapi karena tidak ada templat namespace dan namespaces tidak dapat dilewatkan sebagai parameter templat, menggunakan kelas dengan apa-apa kecuali anggota statis sangat umum.


3
Jawaban-jawaban lain mengaburkan baris terakhir OP. Ruang nama cukup banyak bernama lingkup, jadi jika Anda memerlukan kontrol akses, kelas adalah satu-satunya alat yang tersedia.
cmannett85

2
@ cbamber85 agar adil, saya telah menempatkan kalimat itu hanya setelah membaca dua jawaban pertama.
PersonalNexus

@PersonalNexus Ah cukup adil, saya tidak sadar!
cmannett85

10
@ cbamber85: Itu tidak sepenuhnya benar. Anda akan mendapatkan enkapsulasi yang lebih baik dengan meletakkan fungsi "pribadi" dalam file implementasi, sehingga pengguna tidak dapat melihat bahkan deklarasi pribadi.
UncleBens

2
+1 Templat memang merupakan titik di mana ruang nama masih kekurangan fitur.
Chris mengatakan Reinstate Monica

13

Kembali pada hari saya perlu mengambil kelas FORTRAN. Saya tahu bahasa-bahasa penting lainnya saat itu, jadi saya pikir saya bisa melakukan FORTRAN tanpa banyak belajar. Ketika saya menyerahkan pekerjaan rumah pertama saya, profesor mengembalikannya kepada saya dan meminta untuk mengulanginya: dia mengatakan bahwa program Pascal yang ditulis dalam sintaks FORTRAN tidak dihitung sebagai pengiriman yang valid.

Masalah serupa sedang terjadi di sini: menggunakan kelas statis untuk meng-host fungsi utilitas di C ++ adalah idiom asing untuk C ++.

Seperti yang Anda sebutkan dalam pertanyaan Anda, menggunakan kelas statis untuk fungsi utilitas di C # adalah suatu keharusan: fungsi yang berdiri sendiri sama sekali bukan opsi di C #. Bahasa yang diperlukan untuk mengembangkan pola untuk memungkinkan programmer mendefinisikan fungsi yang berdiri sendiri dalam beberapa cara lain - yaitu, dalam kelas utilitas statis. Ini adalah trik Java yang diambil kata-demi-kata: misalnya, java.lang.Math dan System.Math dari .NET hampir isomorfik.

C ++, bagaimanapun, menawarkan ruang nama, fasilitas berbeda untuk mencapai tujuan yang sama, dan secara aktif menggunakannya dalam implementasi perpustakaan standarnya. Menambahkan lapisan tambahan kelas statis tidak hanya tidak perlu, tetapi juga agak berlawanan dengan pembaca tanpa C # atau latar belakang Java. Dalam arti tertentu, Anda memperkenalkan "terjemahan pinjaman" ke dalam bahasa untuk sesuatu yang dapat diekspresikan secara asli.

Ketika fungsi Anda perlu berbagi data, situasinya berbeda. Karena fungsi Anda tidak lagi terkait , pola Singleton menjadi cara yang disukai untuk menangani persyaratan ini.


6
+1, kecuali untuk merekomendasikan lajang. Mereka tidak disukai di C ++! Fungsinya mungkin dalam kelas jika mereka perlu berbagi keadaan, tetapi kelas tidak boleh lajang kecuali jika benar- benar perlu. Dan biasanya tidak.
Maks.

@ Maxpm Jangan salah paham, singleton lebih disukai hanya untuk variabel statis global. Selain itu, tidak ada yang "disukai" tentang itu.
dasblinkenlight

2
Ada artikel bagus ini, Singletons: Memecahkan Masalah yang Tidak Anda Ketahui Belum Pernah Anda Miliki Sejak 1995 . Ringkasnya dikatakan bahwa menggabungkan instance terkenal ke kelas itu sendiri tidak perlu membatasi fleksibilitas Anda dan tidak memberi Anda apa pun. Sebagian besar waktu hanya memiliki kelas dan memiliki contoh bersama di suatu tempat, tetapi jangan membuatnya singleton.
Jan Hudec

Saya tidak yakin "idiom asing" adalah istilah yang tepat. Ini adalah ungkapan yang sangat umum. Saya pikir beberapa tim pemrograman menggunakan e2 dari Stroustrup ...
Nick Keighley

10

Mungkin Anda perlu bertanya mengapa Anda ingin kelas yang serba statis?

Satu-satunya alasan yang dapat saya pikirkan adalah bahwa bahasa lain (Java dan C #) yang sangat banyak 'semuanya adalah kelas' memerlukannya. Bahasa-bahasa ini tidak dapat membuat fungsi tingkat atas sama sekali, jadi mereka menciptakan trik untuk menjaganya, dan itu adalah fungsi anggota statis. Mereka sedikit solusi, tetapi C ++ tidak memerlukan hal seperti itu, Anda dapat membuat fungsi tingkat atas, independen yang baru secara langsung.

Jika Anda membutuhkan fungsi yang beroperasi pada item data tertentu, maka masuk akal untuk menggabungkannya ke dalam kelas yang menyimpan data, tetapi kemudian, ini berhenti menjadi fungsi dan mulai menjadi anggota kelas yang beroperasi pada data kelas.

Jika Anda memiliki fungsi yang tidak beroperasi pada tipe data tertentu (saya menggunakan kata di sini karena kelas adalah cara untuk mendefinisikan tipe data baru) maka itu benar-benar merupakan anti-pola untuk mendorong mereka ke dalam kelas, tidak lain dari tujuan semantik.


10
Memang - saya akan mengatakan bahwa "kelas dengan semua anggota statis" di Jawa sebenarnya adalah peretasan untuk menyiasati keterbatasan Jawa.
Charles Salvia

@CharlesSalvia: Saya bersikap sopan :) Saya memiliki perasaan buruk yang sama tentang main () menjadi bagian dari kelas java juga, meskipun saya mengerti mengapa mereka melakukan itu.
gbjbaanb

Ini sebenarnya sepertinya memberikan jawaban mengapa Anda ingin kelas serba statis. Atau apakah saya salah paham dengan Anda? Sebagai contoh, saya perlu memegang variabel yang nilainya dapat berubah yang dibaca oleh satu kelas (yang ingin saya simpan dalam ROM) dan ditulis oleh kelas lain (yang akan di RAM). Cukup menempatkan di lokasi ram yang diketahui tidak cukup - membungkusnya (dan itu adalah pengakses) di kelas memungkinkan saya untuk menyempurnakan kontrol akses. Cukup banyak apa yang Anda jelaskan di paragraf kedua Anda.
iheanyi

5

Setidaknya di permukaan, metode statis di kelas tampaknya tidak bisa dibedakan dari fungsi bebas di namespace.

Dengan kata lain memiliki kelas bukan namespace tidak memiliki keuntungan.

Mengapa demikian preferensi untuk yang terakhir?

Untuk satu hal itu menghemat Anda mengetik staticsepanjang waktu, meskipun itu bisa dibilang manfaat yang agak kecil.

Manfaat utama adalah itu adalah alat paling tidak ampuh untuk pekerjaan itu. Kelas dapat digunakan untuk membuat objek, mereka dapat digunakan sebagai jenis variabel atau sebagai argumen templat. Tak satu pun dari itu adalah fitur yang Anda inginkan untuk koleksi fungsi Anda. Jadi lebih baik menggunakan alat yang tidak memiliki fitur-fitur itu, sehingga kelas tidak dapat disalahgunakan secara tidak sengaja.

Mengikuti dari yang menggunakan namespace membuatnya juga segera jelas bagi pengguna kode Anda bahwa ini adalah kumpulan fungsi dan bukan cetak biru untuk membuat objek dari.


5

... namun beberapa komentar mengatakan bahwa fungsi bebas lebih disukai, bahkan menyarankan kelas statis adalah anti-pola. Mengapa begitu di C ++? Setidaknya di permukaan, metode statis di kelas tampaknya tidak bisa dibedakan dari fungsi bebas di namespace. Mengapa demikian preferensi untuk yang terakhir?

Kelas yang serba statis akan menyelesaikannya, tetapi rasanya seperti mengendarai truk semi 53 kaki ke toko bahan makanan untuk membeli keripik dan salsa ketika sedan empat pintu akan melakukan (yaitu, itu terlalu banyak). Kelas datang dengan sedikit overhead tambahan dan keberadaan mereka mungkin memberi seseorang kesan bahwa instantiasi mungkin ide yang bagus. Fungsi gratis yang ditawarkan oleh C ++ (dan C, di mana semua fungsi gratis) tidak melakukan itu; mereka hanya fungsi dan tidak ada yang lain.

Apakah ada yang berbeda, jika koleksi fungsi utilitas memerlukan beberapa data bersama, mis. Cache yang dapat disimpan dalam bidang statis pribadi?

Tidak juga. Tujuan asli dari staticdalam C (dan kemudian C ++) adalah untuk menawarkan penyimpanan persisten yang dapat digunakan di semua tingkat ruang lingkup:

int counter() {
    static int value = 0;
    value++;
}

Anda bisa mengambil cakupan ke tingkat file, yang membuatnya terlihat oleh semua ruang lingkup yang lebih sempit tetapi tidak di luar file:

static int value = 0;

int up() { value++; }
int down() { value--; }

Seorang anggota kelas statis pribadi di C ++ melayani tujuan yang sama tetapi terbatas pada ruang lingkup kelas. Teknik yang digunakan dalam counter()contoh di atas juga berfungsi di dalam metode C ++ dan sesuatu yang saya benar-benar sarankan lakukan jika variabel tidak perlu terlihat oleh seluruh kelas.


Saya kira sekelompok fungsi utilitas yang tidak terkait yang tidak berbagi data harus dikelompokkan lebih baik dalam namespace. Tetapi jika Anda memiliki sekelompok fungsi utilitas yang sangat erat yang memerlukan akses ke data bersama dan mungkin beberapa kontrol akses, kelas statis adalah pilihan terbaik. Suatu statis dalam suatu fungsi tidak reentrant. Menggunakan modul global ... sedikit lebih baik dalam konteks ini, tapi saya masih berpikir semua kelas statis adalah solusi terbaik jika Anda memiliki persyaratan yang saya jelaskan.
iheanyi

Jika mereka berbagi data, mungkinkah mereka lebih baik sebagai kelas tanpa metode statis?
Nick Keighley

@NickKeighley Itu tergantung pada siapa yang memenangkan perdebatan tentang apakah lebih baik untuk membuat satu instance dan menyebarkannya ke semua yang membutuhkannya atau hanya membuatnya statis di kelas.
Blrfl

1

Jika suatu fungsi tidak mempertahankan keadaan dan reentrant, tampaknya tidak ada banyak gunanya mendorongnya di dalam kelas, (kecuali dipaksa oleh bahasa). Jika fungsi mempertahankan beberapa keadaan, (mis. Itu hanya dapat dibuat aman dengan menggunakan mutex statis), maka metode statis pada kelas tampak sesuai.


1

Banyak wacana tentang topik di sini masuk akal, meskipun ada sesuatu yang sangat mendasar tentang C ++ yang membuat namespacesdan classes / structs sangat berbeda.

Kelas statis (kelas di mana semua anggota statis, dan kelas tidak akan pernah dipakai) adalah objek itu sendiri. Mereka bukan hanya namespaceberisi fungsi.

Meta-pemrograman template memungkinkan kita untuk menggunakan kelas statis sebagai objek waktu kompilasi.

Pertimbangkan ini:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Untuk menggunakannya kita perlu fungsi yang terkandung di dalam kelas. Namespace tidak akan berfungsi di sini. Mempertimbangkan:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Sekarang untuk menggunakannya:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Selama pengalokasi baru memperlihatkan allocatedan releasefungsi yang kompatibel, beralih ke pengalokasi baru itu mudah.

Ini tidak dapat dicapai dengan ruang nama.

Apakah Anda selalu membutuhkan fungsi untuk menjadi bagian dari kelas? Tidak.

Apakah menggunakan kelas statis merupakan anti-pola? Itu tergantung pada konteksnya.

Apakah ada yang berbeda, jika koleksi fungsi utilitas memerlukan beberapa data bersama, mis. Cache yang dapat disimpan dalam bidang statis pribadi?

Dalam hal ini apa yang ingin Anda capai kemungkinan besar akan dilayani melalui pemrograman berorientasi objek.


Penggunaan kata kunci inline berlebihan di sini karena metode yang didefinisikan dalam definisi kelas secara inline secara implisit. Lihat en.cppreference.com/w/cpp/language/inline .
Trevor

1

Seperti yang dikatakan John Carmack dengan terkenal:

"Kadang-kadang, implementasi elegan hanya fungsi. Bukan metode. Bukan kelas. Bukan kerangka kerja. Hanya fungsi."

Saya pikir itu cukup banyak. Mengapa Anda membuatnya dengan paksa kelas jika itu jelas bukan kelas? C ++ memiliki kemewahan yang sebenarnya bisa Anda gunakan fungsinya; di Jawa, semuanya adalah metode. Maka Anda memerlukan kelas utilitas, dan banyak dari mereka.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.