Apa yang akan menjadi set hacks preprocessor yang bagus (kompatibel ANSI C89 / ISO C90) yang memungkinkan semacam orientasi objek yang jelek (tetapi dapat digunakan) di C?
Saya akrab dengan beberapa bahasa berorientasi objek yang berbeda, jadi jangan merespons dengan jawaban seperti "Pelajari C ++!". Saya telah membaca " Pemrograman Berorientasi Objek Dengan ANSI C " (berhati-hatilah: format PDF ) dan beberapa solusi menarik lainnya, tetapi saya lebih tertarik dengan Anda :-)!
Lihat juga Bisakah Anda menulis kode berorientasi objek dalam C?
Jawaban:
C Object System (COS) terdengar menjanjikan (masih dalam versi alpha). Ini mencoba untuk meminimalkan konsep yang tersedia demi kesederhanaan dan fleksibilitas: pemrograman berorientasi objek yang seragam termasuk kelas terbuka, metaclasses, metaclasses properti, generik, multimethod, delegasi, kepemilikan, pengecualian, kontrak dan penutupan. Ada draft paper (PDF) yang menjelaskannya.
Pengecualian dalam C adalah implementasi C89 dari TRY-CATCH-FINALLY yang ditemukan dalam bahasa OO lainnya. Itu datang dengan testuite dan beberapa contoh.
Baik oleh Laurent Deniau, yang bekerja banyak pada OOP di C .
sumber
Saya akan menyarankan terhadap preprocessor (ab) gunakan untuk mencoba dan membuat sintaks C lebih seperti itu dari bahasa yang lebih berorientasi objek. Pada level paling dasar, Anda hanya menggunakan struct sederhana sebagai objek dan membagikannya dengan pointer:
Untuk mendapatkan hal-hal seperti warisan dan polimorfisme, Anda harus bekerja lebih keras. Anda dapat melakukan pewarisan manual dengan membuat anggota pertama struktur menjadi instance dari superclass, dan kemudian Anda dapat melemparkan pointer ke kelas basis dan turunan secara bebas:
Untuk mendapatkan polimorfisme (yaitu fungsi virtual), Anda menggunakan pointer fungsi, dan tabel penunjuk fungsi opsional, juga dikenal sebagai tabel virtual atau tabel:
Dan itulah bagaimana Anda melakukan polimorfisme dalam C. Ini tidak cantik, tetapi itu berhasil. Ada beberapa masalah lengket yang melibatkan lemparan pointer antara kelas basis dan turunan, yang aman selama kelas basis adalah anggota pertama dari kelas turunan. Multiple inheritance jauh lebih sulit - dalam hal ini, untuk membedakan antara kelas dasar selain yang pertama, Anda perlu menyesuaikan pointer secara manual berdasarkan offset yang tepat, yang benar-benar rumit dan rawan kesalahan.
Hal lain (rumit) yang dapat Anda lakukan adalah mengubah tipe dinamis suatu objek saat runtime! Anda baru saja menetapkan ulang pointer vtable baru. Anda bahkan dapat secara selektif mengubah beberapa fungsi virtual sambil menjaga yang lain, menciptakan tipe hybrid baru. Berhati-hatilah untuk membuat vtable baru alih-alih memodifikasi global vtable, jika tidak, Anda akan secara tidak sengaja memengaruhi semua objek dari jenis tertentu.
sumber
struct derived {struct base super;};
jelas untuk menebak cara kerjanya, karena urutan byte itu benar.Saya pernah bekerja dengan perpustakaan C yang diimplementasikan dengan cara yang menurut saya cukup elegan. Mereka telah menulis, dalam C, cara untuk mendefinisikan objek, kemudian mewarisi dari mereka sehingga mereka dapat diperluas sebagai objek C ++. Ide dasarnya adalah ini:
Warisan sulit untuk dijelaskan, tetapi pada dasarnya begini:
Kemudian di file lain:
Maka Anda bisa membuat van dibuat dalam memori, dan digunakan oleh kode yang hanya tahu tentang kendaraan:
Ini bekerja dengan indah, dan file .h mendefinisikan dengan tepat apa yang harus Anda lakukan dengan setiap objek.
sumber
Desktop GNOME untuk Linux ditulis dalam berorientasi objek C, dan memiliki model objek yang disebut " GObject " yang mendukung properti, warisan, polimorfisme, serta beberapa barang lain seperti referensi, penanganan acara (disebut "sinyal"), runtime mengetik, data pribadi, dll.
Ini termasuk preprocessor hacks untuk melakukan hal-hal seperti mengetik di hierarki kelas, dll. Berikut adalah contoh kelas yang saya tulis untuk GNOME (hal-hal seperti gchar adalah typedefs):
Sumber Kelas
Header kelas
Di dalam struktur GObject ada bilangan bulat GType yang digunakan sebagai angka ajaib untuk sistem pengetikan dinamis GLib (Anda dapat melemparkan seluruh struct ke "GType" untuk menemukan jenisnya).
sumber
Saya dulu melakukan hal semacam ini di C, sebelum saya tahu apa itu OOP.
Berikut ini adalah contoh, yang mengimplementasikan buffer data yang tumbuh sesuai permintaan, mengingat ukuran minimum, peningkatan dan ukuran maksimum. Implementasi khusus ini adalah "elemen" berbasis, yang mengatakan itu dirancang untuk memungkinkan koleksi seperti daftar dari semua jenis C, bukan hanya byte-buffer panjang variabel.
Idenya adalah bahwa objek instantiated menggunakan xxx_crt () dan dihapus menggunakan xxx_dlt (). Setiap metode "anggota" membutuhkan pointer yang diketik khusus untuk beroperasi.
Saya menerapkan daftar tertaut, buffer siklik, dan sejumlah hal lain dengan cara ini.
Saya harus akui, saya tidak pernah memikirkan bagaimana menerapkan warisan dengan pendekatan ini. Saya membayangkan bahwa beberapa perpaduan yang ditawarkan oleh Kieveli mungkin merupakan jalan yang baik.
dtb.c:
dtb.h
PS: vint hanyalah tip int - saya menggunakannya untuk mengingatkan saya bahwa panjangnya adalah variabel dari platform ke platform (untuk porting).
sumber
Sedikit di luar topik, tetapi kompiler C ++ asli, Cfront , mengkompilasi C ++ ke C dan kemudian ke assembler.
Diawetkan di sini .
sumber
Jika Anda berpikir tentang metode yang memanggil objek sebagai metode statis yang melewatkan implisit
this
ke fungsi itu dapat membuat berpikir OO dalam C lebih mudah.Sebagai contoh:
menjadi:
Atau semacam itu.
sumber
string->length(s);
ffmpeg (toolkit untuk pemrosesan video) ditulis dalam C lurus (dan bahasa rakitan), tetapi menggunakan gaya berorientasi objek. Ini penuh dengan struct dengan pointer fungsi. Ada satu set fungsi pabrik yang menginisialisasi struct dengan petunjuk "metode" yang sesuai.
sumber
Jika Anda benar-benar berpikir secara hati-hati, bahkan standar penggunaan C perpustakaan OOP - menganggap
FILE *
sebagai contoh:fopen()
menginisialisasi sebuahFILE *
objek, dan Anda menggunakannya menggunakan metode anggotafscanf()
,fprintf()
,fread()
,fwrite()
dan lain-lain, dan akhirnya menyelesaikan denganfclose()
.Anda juga bisa menggunakan cara pseudo-Objective-C yang tidak sulit juga:
Menggunakan:
Inilah yang mungkin dihasilkan dari beberapa kode Objective-C seperti ini, jika penerjemah Objective-C-to-C yang cukup lama digunakan:
sumber
__attribute__((constructor))
harus dilakukanvoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
juga mengembalikanFILE *
untuk contoh lain.Saya pikir apa yang diposting Adam Rosenfield adalah cara yang benar untuk melakukan OOP di C. Saya ingin menambahkan bahwa apa yang dia tunjukkan adalah implementasi objek. Dengan kata lain implementasi yang sebenarnya akan dimasukkan ke dalam
.c
file, sedangkan antarmuka akan dimasukkan ke dalam.h
file header . Misalnya, menggunakan contoh monyet di atas:Antarmuka akan terlihat seperti:
Anda dapat melihat di
.h
file antarmuka Anda hanya mendefinisikan prototipe. Anda kemudian dapat mengkompilasi bagian ".c
file" implementasi ke perpustakaan statis atau dinamis. Ini menciptakan enkapsulasi dan Anda juga dapat mengubah implementasinya sesuka hati. Pengguna objek Anda perlu tahu hampir tidak ada tentang penerapannya. Ini juga menempatkan fokus pada desain keseluruhan objek.Ini keyakinan pribadi saya bahwa oop adalah cara untuk mengkonseptualisasikan struktur kode Anda dan penggunaan kembali dan benar-benar tidak ada hubungannya dengan hal-hal lain yang ditambahkan ke c ++ seperti overloading atau template. Ya itu adalah fitur berguna yang sangat bagus tetapi mereka tidak mewakili pemrograman berorientasi objek apa sebenarnya.
sumber
typedef struct Monkey {} Monkey;
Apa gunanya mengetik setelah itu dibuat?struct _monkey
hanyalah sebuah prototipe. Definisi tipe aktual didefinisikan dalam file implementasi (file .c). Ini menciptakan efek enkapsulasi dan memungkinkan pengembang API untuk mendefinisikan kembali struktur monyet di masa depan tanpa memodifikasi API. Pengguna API hanya perlu peduli dengan metode yang sebenarnya. Perancang API menangani implementasi termasuk bagaimana objek / struct diletakkan. Jadi detail objek / struct disembunyikan dari pengguna (tipe buram).int getCount(ObjectType obj)
dll jika Anda memilih untuk mendefinisikan struct dalam file implementasi.Rekomendasi saya: tetap sederhana. Salah satu masalah terbesar yang saya miliki adalah memelihara perangkat lunak yang lebih lama (terkadang lebih dari 10 tahun). Jika kodenya tidak sederhana, itu bisa sulit. Ya, orang dapat menulis OOP yang sangat berguna dengan polimorfisme dalam C, tetapi bisa sulit dibaca.
Saya lebih suka objek sederhana yang merangkum beberapa fungsi yang terdefinisi dengan baik. Contoh yang bagus untuk hal ini adalah GLIB2 , misalnya tabel hash:
Kuncinya adalah:
sumber
Jika saya akan menulis OOP di CI mungkin akan pergi dengan desain pseudo- Pimpl . Alih-alih meneruskan pointer ke struct, Anda akhirnya melewati pointer ke pointer ke struct. Ini membuat konten menjadi buram dan memfasilitasi polimorfisme dan pewarisan.
Masalah sebenarnya dengan OOP di C adalah apa yang terjadi ketika variabel keluar dari ruang lingkup. Tidak ada destruktor yang dihasilkan kompiler dan yang dapat menyebabkan masalah. Makro mungkin bisa membantu, tetapi akan selalu jelek untuk dilihat.
sumber
if
pernyataan dan melepaskannya di akhir. Sebagai contohif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Cara lain untuk memprogram dalam gaya berorientasi objek dengan C adalah dengan menggunakan generator kode yang mengubah bahasa domain spesifik menjadi C. Seperti yang dilakukan dengan TypeScript dan JavaScript untuk membawa OOP ke js.
sumber
Keluaran:
Berikut ini adalah pertunjukan tentang apa itu pemrograman OO dengan C.
Ini nyata, C murni, tidak ada makro preprosesor. Kami memiliki pewarisan, polimorfisme, dan enkapsulasi data (termasuk data pribadi ke kelas atau objek). Tidak ada peluang untuk setara kualifikasi yang dilindungi, yaitu, data pribadi juga bersifat pribadi di rantai pewarisan juga. Tapi ini bukan ketidaknyamanan karena saya pikir itu tidak perlu.
CPolygon
tidak dipakai karena kami hanya menggunakannya untuk memanipulasi objek dari rantai bawaan yang memiliki aspek umum tetapi implementasi yang berbeda dari mereka (Polimorfisme).sumber
@Adam Rosenfield memiliki penjelasan yang sangat bagus tentang bagaimana mencapai OOP dengan C
Selain itu, saya akan merekomendasikan Anda untuk membaca
1) pjsip
Pustaka C yang sangat bagus untuk VoIP. Anda dapat mempelajari cara mencapai OOP melalui struct dan tabel pointer fungsi
2) iOS Runtime
Pelajari bagaimana iOS Runtime menggerakkan Objective C. Ini mencapai OOP melalui isa pointer, kelas meta
sumber
Bagi saya orientasi objek dalam C harus memiliki fitur-fitur ini:
Enkapsulasi dan penyembunyian data (dapat dicapai dengan menggunakan struct / pointer buram)
Warisan dan dukungan untuk polimorfisme (pewarisan tunggal dapat dicapai dengan menggunakan struct - pastikan basis abstrak tidak instantiable)
Fungsi konstruktor dan destruktor (tidak mudah dicapai)
Pengecekan tipe (setidaknya untuk tipe yang ditentukan pengguna karena C tidak menerapkan apa pun)
Penghitungan referensi (atau sesuatu untuk mengimplementasikan RAII )
Dukungan terbatas untuk penanganan pengecualian (setjmp dan longjmp)
Di atas semua itu harus bergantung pada spesifikasi ANSI / ISO dan tidak boleh bergantung pada fungsi spesifik kompiler.
sumber
Lihatlah http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Jika tidak ada yang lain membaca dokumentasi adalah pengalaman yang mencerahkan.
sumber
Saya agak terlambat ke pesta di sini, tetapi saya ingin menghindari kedua makro ekstrem - terlalu banyak atau terlalu banyak kode mengaburkan, tetapi beberapa makro yang jelas dapat membuat kode OOP lebih mudah untuk dikembangkan dan dibaca:
Saya pikir ini memiliki keseimbangan yang baik, dan kesalahan yang dihasilkannya (setidaknya dengan opsi default gcc 6.3) untuk beberapa kesalahan yang lebih mungkin membantu daripada membingungkan. Intinya adalah untuk meningkatkan produktivitas programmer, bukan?
sumber
Jika Anda perlu menulis sedikit kode coba ini: https://github.com/fulminati/class-framework
sumber
Saya juga sedang mengerjakan ini berdasarkan pada solusi makro. Jadi itu hanya untuk yang paling berani, saya kira ;-) Tapi itu sudah cukup bagus, dan saya sudah mengerjakan beberapa proyek di atasnya. Ini berfungsi agar Anda pertama kali menentukan file header terpisah untuk setiap kelas. Seperti ini:
Untuk mengimplementasikan kelas, Anda membuat file header untuk itu dan file C tempat Anda menerapkan metode:
Di header yang Anda buat untuk kelas, Anda menyertakan header lain yang Anda butuhkan dan menentukan jenis dll yang terkait dengan kelas. Baik di header kelas dan di file C Anda menyertakan file spesifikasi kelas (lihat contoh kode pertama) dan X-makro. Makro X ini ( 1 , 2 , 3 dll.) Akan memperluas kode ke struct kelas aktual dan deklarasi lainnya.
Untuk mewarisi kelas,
#define SUPER supername
dan tambahkansupername__define \
sebagai baris pertama dalam definisi kelas. Keduanya pasti ada di sana. Ada juga dukungan JSON, sinyal, kelas abstrak, dll.Untuk membuat objek, cukup gunakan
W_NEW(classname, .x=1, .y=2,...)
. Inisialisasi didasarkan pada inisialisasi struct yang diperkenalkan pada C11. Ini bekerja dengan baik dan semua yang tidak terdaftar diatur ke nol.Untuk memanggil metode, gunakan
W_CALL(o,method)(1,2,3)
. Sepertinya panggilan fungsi tingkat tinggi tetapi ini hanya makro. Perluasan((o)->klass->method(o,1,2,3))
yang merupakan hack yang sangat bagus.Lihat Dokumentasi dan kode itu sendiri .
Karena kerangka kerja membutuhkan beberapa kode boilerplate, saya menulis skrip Perl (wobject) yang berfungsi. Jika Anda menggunakannya, Anda bisa menulis
dan itu akan membuat file spesifikasi kelas, header kelas, dan file C, yang termasuk di
Point_impl.c
mana Anda mengimplementasikan kelas. Menghemat banyak pekerjaan, jika Anda memiliki banyak kelas sederhana tetapi masih semuanya dalam C. wobject adalah pemindai berbasis ekspresi reguler yang sangat sederhana yang mudah untuk beradaptasi dengan kebutuhan spesifik, atau ditulis ulang dari awal.sumber
Proyek Dynace open-source melakukan hal itu. Itu di https://github.com/blakemcbride/Dynace
sumber
Anda dapat mencoba COOP , kerangka kerja yang ramah programmer untuk OOP di C, fitur Kelas, Pengecualian, Polimorfisme, dan Manajemen Memori (penting untuk kode Embedded). Ini sintaks yang relatif ringan, lihat tutorial di Wiki di sana.
sumber