Apakah praktik yang buruk untuk menggunakan kompiler C ++ hanya untuk fungsi yang berlebihan?

60

Jadi saya sedang mengerjakan desain perangkat lunak menggunakan C untuk prosesor tertentu. Tool-kit termasuk kemampuan untuk mengkompilasi C serta C ++. Untuk apa yang saya lakukan, tidak ada alokasi memori dinamis yang tersedia di lingkungan ini dan program ini secara keseluruhan cukup sederhana. Belum lagi bahwa perangkat ini hampir tidak memiliki daya atau sumber daya prosesor. Benar-benar tidak ada kebutuhan kuat untuk menggunakan C ++ apa pun.

Yang sedang berkata, ada beberapa tempat di mana saya melakukan fungsi overloading (fitur C ++). Saya perlu mengirim beberapa jenis data yang berbeda dan tidak merasa ingin menggunakan printfpemformatan gaya dengan semacam %sargumen (atau apa pun). Saya telah melihat beberapa orang yang tidak memiliki akses ke kompiler C ++ melakukan printfhal itu, tetapi dalam kasus saya dukungan C ++ tersedia.

Sekarang saya yakin saya mungkin mendapatkan pertanyaan mengapa saya harus membebani fungsi untuk memulai. Jadi saya akan mencoba menjawabnya sekarang. Saya perlu mengirimkan berbagai jenis data keluar dari port serial sehingga saya memiliki beberapa kelebihan yang mengirimkan tipe data berikut:

unsigned char*
const char*
unsigned char
const char

Saya lebih suka tidak memiliki satu metode yang menangani semua hal ini. Ketika saya memanggil fungsi saya hanya ingin mengirimkan port serial, saya tidak punya banyak sumber daya jadi saya tidak ingin melakukan APA SAJA selain transmisi saya.

Orang lain melihat program saya dan bertanya, "mengapa Anda menggunakan file CPP?" Jadi, itulah satu-satunya alasan saya. Apakah itu praktik buruk?

Memperbarui

Saya ingin menjawab beberapa pertanyaan yang diajukan:

Jawaban obyektif untuk dilema Anda akan tergantung pada:

  1. Apakah ukuran yang dapat dieksekusi tumbuh secara signifikan jika Anda menggunakan C ++.

Pada saat ini ukuran executable mengkonsumsi 4.0% dari memori program (dari 5248 bytes) dan 8,3% dari memori data (dari 342 bytes). Yaitu, mengkompilasi untuk C ++ ... Saya tidak tahu seperti apa tampilannya untuk C karena saya belum pernah menggunakan kompiler C. Saya tahu bahwa program ini tidak akan tumbuh lagi, jadi untuk seberapa terbatas sumber daya saya akan mengatakan saya baik-baik saja di sana ...

  1. Apakah ada dampak negatif yang nyata pada kinerja jika Anda menggunakan C ++.

Ya kalau ada, saya belum melihat apa-apa ... tapi sekali lagi itu sebabnya saya menanyakan pertanyaan ini karena saya tidak sepenuhnya mengerti.

  1. Apakah kode dapat digunakan kembali pada platform yang berbeda di mana hanya kompiler C yang tersedia.

Saya tahu bahwa jawabannya adalah tidak . Kami sebenarnya mempertimbangkan untuk pindah ke prosesor yang berbeda, tetapi hanya prosesor berbasis ARM yang lebih kuat (yang semuanya saya tahu sebenarnya memiliki rantai alat penyusun C ++).

Mengintip
sumber
59
Saya dikenal menggunakan C ++ untuk proyek yang hanya menggunakan fitur C hanya supaya saya dapat memiliki //komentar. Jika berhasil, mengapa tidak?
Jules
76
Praktik buruknya adalah membatasi diri Anda ke C ketika Anda memiliki penggunaan yang baik untuk fitur yang tidak disediakannya.
Jerry Coffin
35
Ketika Anda mengatakan "gunakan kompiler C ++" maksud Anda "gunakan C ++". Katakan saja. Anda tidak dapat mengkompilasi C dengan kompiler C ++, tetapi Anda dapat beralih dari C ke C ++ dengan mudah, yang sebenarnya Anda lakukan.
user253751
4
"Untuk apa yang saya lakukan, tidak ada alokasi memori dinamis pada prosesor dan program ini secara keseluruhan cukup sederhana. Belum lagi bahwa perangkat ini hampir tidak memiliki daya atau sumber daya prosesor. Benar-benar tidak ada kebutuhan yang kuat untuk menggunakan C ++ apa pun." Saya harap kalimat pertama dari kedua kalimat itu seharusnya menjadi alasan untuk tidak menggunakan C ++, karena itu adalah kalimat yang sangat buruk. C ++ baik-baik saja untuk digunakan dengan sistem embedded.
Pharap
21
@ Jules Saya yakin Anda tahu ini dan berpikir kembali beberapa saat, tetapi jika seseorang membaca ini tidak: //komentar telah dalam standar C sejak C99.
Davislor

Jawaban:

77

Aku tidak akan pergi sejauh untuk menyebutnya "praktik buruk" per se , tetapi tidak aku yakin itu benar-benar solusi yang tepat untuk masalah Anda. Jika yang Anda inginkan adalah empat fungsi terpisah untuk melakukan empat tipe data Anda, mengapa tidak melakukan apa yang telah dilakukan programmer C sejak dahulu kala:

void transmit_uchar_buffer(unsigned char *buffer);
void transmit_char_buffer(char *buffer);
void transmit_uchar(unsigned char c);
void transmit_char(char c);

Itu efektif apa yang dilakukan oleh kompiler C ++ di belakang layar dan itu bukan biaya overhead yang besar untuk programmer. Hindari semua masalah "mengapa Anda menulis tidak-cukup-C dengan kompiler C ++", dan berarti tidak ada orang lain di proyek Anda yang akan bingung dengan bit C ++ mana yang "diizinkan" dan bit mana yang tidak.

Philip Kendall
sumber
8
Saya akan mengatakan mengapa tidak karena jenis yang ditransmisikan (mungkin) merupakan detail implementasi, jadi menyembunyikannya dan membiarkan kompiler menangani dengan memilih implementasi mungkin menghasilkan kode yang lebih mudah dibaca. Dan jika menggunakan fitur C ++ meningkatkan keterbacaan, lalu mengapa tidak melakukannya?
Jules
29
Satu bahkan dapat #definemengirimkan () menggunakan C11_Generic
Deduplicator
16
@ Jules Karena itu sangat membingungkan seperti apa fitur C ++ diizinkan untuk digunakan dalam proyek ini. Apakah potensi perubahan yang mengandung objek ditolak? Bagaimana dengan templat? Komentar gaya C ++? Tentu, Anda dapat mengatasinya dengan dokumen gaya pengkodean, tetapi jika semua yang Anda lakukan adalah kasus sederhana kelebihan fungsi, maka tulis saja C sebagai gantinya.
Philip Kendall
25
@Phillip "Komentar gaya C ++" telah berlaku selama lebih dari satu dekade.
David Conrad
12
@ Jules: sementara jenis yang sedang ditransmisikan mungkin merupakan detail implementasi untuk perangkat lunak yang membuat kutipan asuransi, aplikasi OPs terdengar seperti sistem tertanam yang melakukan komunikasi serial, di mana jenis dan datasenya sangat penting.
whatsisname
55

Hanya menggunakan beberapa fitur C ++ sementara memperlakukannya sebagai C tidak umum, tetapi juga tidak pernah terdengar. Bahkan, beberapa orang bahkan tidak menggunakan fitur sama sekali dari C ++, kecuali memeriksa jenis lebih ketat dan lebih kuat. Mereka hanya menulis C (berhati-hati menulis hanya di persimpangan umum C ++ dan C), kemudian kompilasi dengan kompiler C ++ untuk pengecekan tipe, dan kompiler C untuk pembuatan kode (atau tetap dengan kompiler C ++ sepanjang jalan).

Linux adalah contoh basis kode di mana orang-orang secara teratur meminta pengenal seperti classdiubah namanya menjadi klassatau kclass, sehingga mereka dapat mengkompilasi Linux dengan kompiler C ++. Jelas, mengingat pendapat Linus tentang C ++ , mereka selalu tertembak :-D GCC adalah contoh basis kode yang pertama kali diubah menjadi "C ++ - clean C", dan kemudian secara bertahap refactored untuk menggunakan lebih banyak fitur C ++.

Tidak ada yang salah dengan apa yang Anda lakukan. Jika Anda benar-benar paranoid tentang kualitas pembuatan kode kompiler C ++ Anda, Anda dapat menggunakan kompiler seperti Comeau C ++, yang mengkompilasi ke C sebagai bahasa target, dan kemudian menggunakan kompiler C. Dengan begitu, Anda juga dapat melakukan inspeksi langsung pada kode untuk melihat apakah menggunakan C ++ menyuntikkan kode sensitif kinerja yang tidak terduga. Itu tidak seharusnya menjadi kasus untuk overloading, meskipun, yang secara harfiah hanya secara otomatis menghasilkan fungsi yang berbeda bernama - TKI, persis apa yang akan Anda lakukan di C.

Jörg W Mittag
sumber
15

Jawaban obyektif untuk dilema Anda akan tergantung pada:

  1. Apakah ukuran yang dapat dieksekusi tumbuh secara signifikan jika Anda menggunakan C ++.
  2. Apakah ada dampak negatif yang nyata pada kinerja jika Anda menggunakan C ++.
  3. Apakah kode dapat digunakan kembali pada platform yang berbeda di mana hanya kompiler C yang tersedia.

Jika jawaban untuk salah satu pertanyaan adalah "ya", Anda mungkin lebih baik membuat fungsi dengan nama berbeda untuk tipe data yang berbeda dan tetap menggunakan C.

Jika jawaban untuk semua pertanyaan adalah "tidak", saya tidak melihat alasan mengapa Anda tidak harus menggunakan C ++.

R Sahu
sumber
9
Saya harus mengatakan saya belum pernah menemukan kompiler C ++ yang menghasilkan kode yang jauh lebih buruk daripada kompiler C untuk kode yang ditulis dalam subset bersama dari dua bahasa. Kompiler C ++ mendapatkan rep yang buruk untuk ukuran dan kinerja, tetapi pengalaman saya adalah selalu menggunakan fitur C ++ yang tidak sesuai yang menyebabkan masalah ... Terutama, jika Anda khawatir tentang ukuran, jangan gunakan iostreams dan jangan gunakan template, tetapi sebaliknya Anda harus baik-baik saja.
Jules
@ Jules: Hanya untuk apa nilainya (tidak banyak, IMO) Saya telah melihat apa yang dijual sebagai kompiler tunggal untuk C dan C ++ (Turbo C ++ 1.0, jika memori berfungsi) menghasilkan hasil yang sangat berbeda untuk input yang identik. Namun, seperti yang saya pahami, ini sebelum mereka menyelesaikan kompiler C ++ mereka sendiri, jadi meskipun kelihatannya seperti satu kompiler di luar, itu benar-benar memiliki dua kompiler yang sepenuhnya terpisah - satu untuk C, yang lain untuk C ++.
Jerry Coffin
1
@ JerryCoffin Jika ingatanku, produk-produk Turbo tidak pernah memiliki reputasi yang hebat. Dan jika itu versi 1.0, Anda bisa dimaafkan karena tidak terlalu disempurnakan. Jadi mungkin tidak terlalu representatif.
Barmar
10
@Barmar: Sebenarnya, mereka memang memiliki reputasi yang cukup baik untuk sementara waktu. Reputasi mereka yang buruk sekarang terutama disebabkan oleh usia mereka yang belaka. Mereka bersaing dengan kompiler lain dari vintage yang sama - tetapi tidak ada yang memposting pertanyaan tentang bagaimana melakukan sesuatu dengan gcc 1.4 (atau apa pun). Tapi Anda benar - itu tidak terlalu representatif.
Jerry Coffin
1
@ Jules saya berpendapat bahwa template bahkan baik-baik saja. Terlepas dari teori yang populer, instantiasi templat tidak secara implisit meningkatkan ukuran kode, ukuran kode umumnya tidak akan meningkat sampai fungsi dari templat digunakan (dalam hal ini peningkatan ukuran akan sebanding dengan ukuran fungsi) atau kecuali templat tersebut mendeklarasikan variabel statis. Aturan inlining masih berlaku, jadi dimungkinkan untuk menulis templat di mana semua fungsi digarisbawahi oleh kompiler.
Pharap
13

Anda mengajukan pertanyaan seolah-olah kompilasi dengan C ++ hanya memberi Anda akses ke lebih banyak fitur. Ini bukan kasusnya. Mengkompilasi dengan kompiler C ++ berarti bahwa kode sumber Anda ditafsirkan sebagai sumber C ++, dan C ++ adalah bahasa yang berbeda dari C. Keduanya memiliki subset umum yang cukup besar untuk diprogram dengan bermanfaat, tetapi masing-masing memiliki fitur yang kurang dimiliki oleh yang lain, dan itu adalah mungkin untuk menulis kode yang dapat diterima dalam kedua bahasa tetapi ditafsirkan berbeda.

Jika benar-benar yang Anda inginkan adalah fungsi kelebihan beban, maka saya benar-benar tidak melihat titik untuk membingungkan masalah dengan membawa C ++ ke dalamnya. Alih-alih fungsi yang berbeda dengan nama yang sama, yang dibedakan daftar parameternya, hanya menulis fungsi dengan nama yang berbeda.

Adapun kriteria objektif Anda,

  1. Apakah ukuran yang dapat dieksekusi tumbuh secara signifikan jika Anda menggunakan C ++.

Dapat dieksekusi mungkin sedikit lebih besar ketika dikompilasi sebagai C ++, relatif terhadap kode C serupa dibangun seperti itu. Minimal, C ++ executable akan memiliki manfaat meragukan C ++ name-mangling untuk semua nama fungsi, sejauh simbol apa pun disimpan dalam executable. (Dan itulah sebenarnya yang menyebabkan kelebihan Anda di tempat pertama.) Apakah perbedaannya cukup besar untuk menjadi penting bagi Anda adalah sesuatu yang harus Anda tentukan melalui eksperimen.

  1. Apakah ada dampak negatif yang nyata pada kinerja jika Anda menggunakan C ++.

Saya ragu Anda akan melihat perbedaan kinerja nyata untuk kode yang Anda gambarkan vs analog murni-C hipotetis.

  1. Apakah kode dapat digunakan kembali pada platform yang berbeda di mana hanya kompiler C yang tersedia.

Saya akan memberikan sedikit perbedaan pada hal ini: jika Anda ingin menautkan kode yang Anda buat dengan C ++ ke kode lain , maka kode lain itu juga perlu ditulis dalam C ++ dan dibuat dengan C ++, atau Anda perlu untuk membuat ketentuan khusus dalam kode Anda sendiri (menyatakan tautan "C"), yang, selain itu, Anda tidak dapat melakukan sama sekali untuk fungsi kelebihan beban Anda. Di sisi lain, jika kode Anda ditulis dalam C dan dikompilasi sebagai C, maka orang lain dapat menghubungkannya dengan modul C dan C ++. Masalah seperti ini biasanya dapat diatasi dengan membalik tabel, tetapi karena Anda benar-benar tampaknya tidak membutuhkan C ++ maka mengapa menerima masalah seperti itu di tempat pertama?

John Bollinger
sumber
4
"Yang dapat dieksekusi mungkin sedikit lebih besar ketika dikompilasi sebagai C ++ ... sejauh simbol apa pun dipertahankan dalam dieksekusi." Agar adil, meskipun, setiap toolchain optimal yang optimal harus memiliki opsi linker untuk menghapus simbol-simbol ini di build "release", yang berarti mereka hanya mengasapi build debugging Anda. Saya pikir itu bukan kerugian besar. Bahkan, itu lebih sering menguntungkan.
Cody Grey
5

Kembali pada hari itu, menggunakan kompiler C ++ sebagai “a better C” dipromosikan sebagai use case. Bahkan, awal C ++ persis seperti itu. Prinsip desain yang mendasarinya adalah Anda hanya dapat menggunakan fitur yang Anda inginkan dan tidak akan menimbulkan biaya fitur yang tidak Anda gunakan. Jadi, Anda dapat membebani fungsi (dengan menyatakan maksud melalui overloadkata kunci!) Dan sisa proyek Anda tidak hanya akan mengkompilasi dengan baik tetapi akan menghasilkan kode yang tidak lebih buruk daripada yang dihasilkan oleh kompiler C.

Sejak itu, bahasa-bahasa tersebut agak berbeda.

Setiap mallockode C Anda akan menjadi kesalahan jenis ketidakcocokan - bukan masalah dalam kasus Anda tanpa memori dinamis! Demikian juga, semua voidpetunjuk Anda di C akan membuat Anda tersandung, karena Anda harus menambahkan gips eksplisit. Tapi ... mengapa Anda melakukannya dengan cara itu ... Anda akan dibawa ke jalan menggunakan fitur C ++ lebih dan lebih.

Jadi, mungkin saja dengan beberapa pekerjaan ekstra. Tapi itu akan berfungsi sebagai pintu gerbang ke adopsi C ++ skala besar. Dalam beberapa tahun orang-orang akan mengeluh tentang kode warisan Anda yang sepertinya ditulis pada tahun 1989, ketika mereka mengganti mallocpanggilan Anda dengan new, merobek blok kode badan loop untuk hanya memanggil algoritma saja, dan mencaci maki polimorfisme palsu yang tidak aman yang akan telah sepele seandainya kompiler diizinkan untuk melakukannya.

Di sisi lain, Anda tahu bahwa semuanya akan sama jika Anda menuliskannya dalam C, jadi apakah salah untuk menulis dalam C daripada C ++ mengingat keberadaannya? Jika jawabannya "tidak", maka menggunakan fitur cherry-pick dari C ++ juga tidak salah .

JDługosz
sumber
2

Banyak bahasa pemrograman memiliki satu atau lebih budaya yang berkembang di sekitarnya, dan memiliki gagasan khusus tentang apa yang seharusnya menjadi "bahasa". Meskipun mungkin, praktis, dan bermanfaat untuk memiliki bahasa tingkat rendah yang cocok untuk pemrograman sistem yang menambah dialek C yang cocok untuk tujuan tersebut dengan beberapa fitur dari C ++ yang juga cocok, budaya di sekitar dua bahasa tidak khususnya tidak menyukai merger semacam itu.

Saya telah membaca tentang kompiler C tertanam yang menggabungkan beberapa fitur dari C ++, termasuk kemampuan untuk memilikinya

foo.someFunction(a,b,c);

diartikan sebagai, pada dasarnya

typeOfFoo_someFunction(foo, a, b, c);

serta kemampuan untuk membebani fungsi-fungsi statis (masalah pengubahan nama hanya muncul saat dieksporfungsi kelebihan beban). Tidak ada alasan mengapa kompiler C tidak dapat mendukung fungsionalitas seperti itu tanpa memaksakan persyaratan tambahan apa pun pada lingkungan penghubung dan jangka waktu, tetapi penolakan refleksif kompiler C terhadap hampir semua hal dari C ++ untuk menghindari beban lingkungan menyebabkan mereka menolak bahkan fitur yang tidak akan membebankan biaya seperti itu. Sementara itu, budaya C ++ memiliki sedikit minat pada lingkungan apa pun yang tidak dapat mendukung semua fitur bahasa itu. Kecuali atau sampai budaya C berubah untuk menerima fitur-fitur seperti itu, atau budaya C ++ berubah untuk mendorong implementasi himpunan bagian yang ringan, kode "hibrida" cenderung mengalami banyak keterbatasan dari kedua bahasa sementara terbatas pada kemampuannya untuk menuai manfaat beroperasi dalam superset gabungan.

Jika sebuah platform mendukung C dan C ++, kode library tambahan yang diperlukan untuk mendukung C ++ kemungkinan akan membuat executable agak lebih besar, meskipun efeknya tidak akan terlalu bagus. Eksekusi "fitur C" dalam C ++ kemungkinan tidak akan terlalu terpengaruh. Masalah yang lebih besar mungkin adalah bagaimana pengoptimal C dan C ++ menangani berbagai kasus sudut. Perilaku tipe data dalam C sebagian besar ditentukan oleh isi bit yang tersimpan di dalamnya, dan C ++ memiliki kategori struktur yang dikenal (PODS - Plain Old Data Structures) yang semantiknya juga sebagian besar ditentukan oleh bit yang disimpan. Baik standar C dan C ++, bagaimanapun, kadang-kadang memungkinkan perilaku yang bertentangan dengan yang tersirat oleh bit yang disimpan, dan pengecualian untuk perilaku "bit yang disimpan" berbeda antara kedua bahasa.

supercat
sumber
1
Pendapat menarik tetapi tidak menjawab pertanyaan OP.
R Sahu
@RSahu: Kode yang sesuai dengan "budaya" di sekitar bahasa cenderung lebih baik didukung dan lebih mudah beradaptasi dengan berbagai implementasi daripada kode yang tidak. Kultur C dan C ++ sama-sama menentang jenis penggunaan yang disarankan OP, saya akan menambahkan beberapa poin yang lebih spesifik berkenaan dengan pertanyaan spesifik.
supercat
1
Perhatikan bahwa ada grup tertanam / finansial / permainan di komite standar C ++ yang ditujukan untuk menyelesaikan dengan tepat jenis situasi "sedikit minat" yang Anda klaim dibuang.
Yakk
@ Yakk: Saya akui saya tidak terlalu mengikuti dunia C ++, karena minat saya terutama terletak pada C; Saya tahu ada upaya ke arah dialek yang lebih tertanam, tetapi saya tidak berpikir mereka benar-benar berhasil.
supercat
2

Faktor penting lainnya untuk dipertimbangkan: siapa pun yang akan mewarisi kode Anda.

Apakah orang itu akan selalu menjadi programmer C dengan kompiler C? Saya rasa begitu.

Apakah orang itu juga akan menjadi programmer C ++ dengan kompiler C ++? Saya ingin cukup yakin tentang ini sebelum membuat kode saya bergantung pada C ++ hal-hal tertentu.

reinierpost
sumber
2

Polimorfisme adalah fitur yang sangat bagus yang disediakan C ++ secara gratis. Namun, ada aspek lain yang mungkin perlu Anda pertimbangkan saat memilih kompiler. Ada alternatif untuk polimorfisme dalam C tetapi penggunaannya dapat menyebabkan alis terangkat. Ini adalah fungsi variadic, silakan merujuk ke tutorial fungsi Variadic .

Dalam kasus Anda itu akan menjadi sesuatu seperti

enum serialDataTypes
{
 INT =0,
 INT_P =1,
 ....
 }Arg_type_t;
....
....
void transmit(Arg_type_t serial_data_type, ...)
{
  va_list args;
  va_start(args, serial_data_type);

  switch(serial_data_type)
  {
    case INT: 
    //Send integer
    break;

    case INT_P:
    //Send data at integer pointer
    break;
    ...
   }
 va_end(args);
}

Saya suka pendekatan Phillips tapi itu mengotori perpustakaan Anda dengan banyak panggilan. Dengan antarmuka di atas bersih. Itu memang memiliki kekurangan dan pada akhirnya itu adalah masalah pilihan.

nikhil_kotian
sumber
Fungsi variadik terutama melayani kasus penggunaan di mana jumlah argumen dan jenisnya variabel. Kalau tidak, Anda tidak membutuhkannya, dan khususnya, itu berlebihan untuk contoh khusus Anda. Akan lebih mudah untuk menggunakan fungsi biasa yang menerima Arg_type_tdan void *menunjuk ke data. Dengan mengatakan itu, ya, memberikan fungsi (tunggal) argumen yang menunjukkan tipe data memang merupakan alternatif yang layak.
John Bollinger
1

Untuk kasus khusus Anda yang secara statis kelebihan "fungsi" untuk beberapa jenis yang berbeda, Anda dapat mempertimbangkan hanya menggunakan C11 dengan mesin _Genericmakro . Saya merasa itu mungkin cukup untuk kebutuhan Anda yang terbatas.

Dengan menggunakan jawaban Philip Kendall Anda dapat mendefinisikan:

#define transmit(X) _Generic((X),      \
   unsigned char*: transmit_uchar_buffer, \
   char*: transmit_char_buffer,           \
   unsigned char: transmit_uchar,         \
   char: transmit_char) ((X))

dan kode transmit(foo) apa pun jenisnya foo(di antara empat jenis yang tercantum di atas).

Jika Anda hanya peduli GCC (dan kompatibel, misalnya dentang ) compiler, Anda bisa mempertimbangkan nya __builtin_types_compatible_pwtth nya typeofekstensi.

Basile Starynkevitch
sumber
1

Apakah praktik yang buruk untuk menggunakan kompiler C ++ hanya untuk fungsi yang berlebihan?

Sudut IMHO, ya, dan saya harus menjadi skizofrenia untuk menjawab yang satu ini karena saya suka kedua bahasa tetapi tidak ada hubungannya dengan efisiensi, tetapi lebih seperti keamanan dan penggunaan bahasa secara idiomatis.

Sisi C

Dari sudut pandang C, saya merasa sangat boros untuk membuat kode Anda membutuhkan C ++ hanya untuk menggunakan fungsi yang berlebihan. Kecuali jika Anda menggunakannya untuk polimorfisme statis dengan template C ++, itu adalah gula sintaksis sepele yang diperoleh sebagai ganti untuk beralih ke bahasa yang sama sekali berbeda. Lebih jauh lagi jika Anda ingin mengekspor fungsi Anda ke dylib (mungkin atau mungkin tidak menjadi masalah praktis), Anda tidak dapat lagi melakukannya dengan sangat praktis untuk konsumsi yang tersebar luas dengan semua lambang-lambang yang ditandai dengan nama.

Sisi C ++

Dari sudut pandang C ++, Anda seharusnya tidak menggunakan C ++ seperti C dengan fungsi yang berlebihan. Ini bukan dogmatisme gaya tetapi terkait dengan penggunaan praktis C ++ sehari-hari.

Jenis kode C normal Anda hanya masuk akal dan "aman" untuk ditulis jika Anda bekerja melawan sistem tipe C yang melarang hal-hal seperti copy ctors in structs. Setelah Anda bekerja di sistem tipe C ++ yang lebih kaya, fungsi sehari-hari yang bernilai sangat tinggi seperti memsetdan memcpytidak menjadi fungsi yang harus Anda sandarkan sepanjang waktu. Alih-alih, itu adalah fungsi yang biasanya ingin Anda hindari seperti wabah, karena dengan tipe C ++, Anda tidak boleh memperlakukannya seperti bit dan byte mentah yang akan disalin dan diacak serta dibebaskan. Bahkan jika kode Anda hanya menggunakan hal-hal seperti memsetpada primitif dan POD UDT saat ini, saat siapa pun menambahkan ctor ke UDT yang Anda gunakan (termasuk hanya menambahkan anggota yang memerlukannya, sepertistd::unique_ptrmember) terhadap fungsi-fungsi seperti itu atau fungsi virtual atau semacamnya, itu membuat semua kode C-style normal Anda rentan terhadap perilaku yang tidak terdefinisi. Ambillah dari Herb Sutter sendiri:

memcpydan memcmpmelanggar sistem tipe. Menggunakan memcpyuntuk menyalin objek seperti menghasilkan uang menggunakan mesin fotokopi. Menggunakan memcmpuntuk membandingkan objek adalah seperti membandingkan macan tutul dengan menghitung bintik-bintik mereka. Alat dan metode mungkin tampak melakukan pekerjaan itu, tetapi terlalu kasar untuk melakukannya. Objek C ++ adalah semua tentang penyembunyian informasi (bisa dibilang prinsip paling menguntungkan dalam rekayasa perangkat lunak; lihat Butir 11): Objek menyembunyikan data (lihat Butir 41) dan menyusun abstraksi yang tepat untuk menyalin data itu melalui konstruktor dan operator penugasan (lihat Item 52 hingga 55) . Bulldozing atas semua yang memcpyterjadi merupakan pelanggaran serius terhadap penyembunyian informasi, dan sering menyebabkan kebocoran memori dan sumber daya (paling banter), macet (lebih buruk), atau perilaku tidak terdefinisi (terburuk) - Standar C ++ Coding.

Begitu banyak pengembang C yang tidak setuju dengan ini dan memang demikian, karena filosofi ini hanya berlaku jika Anda menulis kode dalam C ++. Anda kemungkinan besar sedang menulis kode yang sangat bermasalah jika Anda menggunakan fungsi seperti memcpysemua waktu dalam kode yang membangun sebagai C ++ , tapi itu baik-baik saja jika Anda melakukannya di C . Kedua bahasa ini sangat berbeda dalam hal ini karena perbedaan dalam sistem tipe. Sangat menggoda untuk melihat subset dari fitur yang dimiliki keduanya dan percaya satu dapat digunakan seperti yang lain, terutama pada sisi C ++, tetapi kode C + (atau kode C -) umumnya jauh lebih bermasalah daripada C dan Kode C ++.

Demikian juga Anda tidak boleh menggunakan, katakanlah, mallocdalam konteks gaya-C (yang menyiratkan tidak ada EH) jika dapat memanggil fungsi C ++ secara langsung yang dapat dilemparkan, sejak itu Anda memiliki titik keluar implisit dalam fungsi Anda sebagai akibat dari pengecualian yang Anda tidak dapat menangkap kode C-style secara efektif, sebelum dapat freemengingatnya. Jadi, setiap kali Anda memiliki file yang membangun sebagai C ++ dengan .cppekstensi atau apa pun dan tidak semua jenis hal-hal seperti malloc, memcpy, memset,qsort, dll, maka ia meminta masalah lebih lanjut di telepon jika belum, kecuali jika itu adalah detail implementasi dari kelas yang hanya bekerja dengan tipe primitif, di mana pada saat itu masih perlu melakukan penanganan pengecualian untuk menjadi pengecualian aman. Jika Anda menulis kode C ++ Anda malah ingin umumnya mengandalkan RAII dan menggunakan hal-hal seperti vector, unique_ptr, shared_ptr, dll, dan menghindari semua normal C-gaya pengkodean bila memungkinkan.

Alasan Anda dapat bermain dengan pisau cukur dalam tipe data C dan x-ray dan bermain dengan bit dan byte mereka tanpa cenderung menyebabkan kerusakan jaminan dalam tim (meskipun Anda masih bisa benar-benar melukai diri sendiri dengan cara apa pun) bukan karena apa C jenis dapat dilakukan, tetapi karena apa yang mereka tidak akan pernah bisa lakukan. Saat Anda memperluas sistem tipe C untuk memasukkan fitur-fitur C ++ seperti ctors, dtors, dan vtables, bersama dengan penanganan pengecualian, semua kode C idiomatik akan dirender jauh, jauh lebih berbahaya daripada saat ini, dan Anda akan melihat jenis baru dari filosofi dan pola pikir berkembang yang akan mendorong gaya pengkodean yang sama sekali berbeda, seperti yang Anda lihat di C ++, yang sekarang mempertimbangkan bahkan menggunakan malpraktik penunjuk mentah untuk kelas yang mengelola memori yang bertentangan dengan, katakanlah, sumber daya yang sesuai dengan RAII unique_ptr. Pola pikir itu tidak berevolusi dari rasa aman absolut. Itu berevolusi dari apa yang perlu secara khusus C ++ aman terhadap fitur-fitur seperti penanganan-pengecualian mengingat apa yang hanya diperbolehkan melalui sistem tipenya.

Pengecualian-Keamanan

Sekali lagi, saat Anda berada di tanah C ++, orang akan mengharapkan kode Anda aman-pengecualian. Orang-orang mungkin mempertahankan kode Anda di masa mendatang, mengingat bahwa itu sudah ditulis dan dikompilasi dalam C ++, dan cukup gunakan std::vector, dynamic_cast, unique_ptr, shared_ptr, dll. Dalam kode yang disebut baik secara langsung atau tidak langsung oleh kode Anda, percaya itu tidak berbahaya karena kode Anda sudah "seharusnya" C ++ kode. Pada saat itu kita harus menghadapi kemungkinan bahwa segala sesuatu akan melempar, dan kemudian ketika Anda mengambil kode C baik-baik saja dan indah seperti ini:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        f(n, x); // some function which, now being a C++ function, may 
                 // throw today or in the future.
        ...
        free(x);
        return success;
    }
    return fail;
}

... sekarang rusak. Perlu ditulis ulang agar aman dari pengecualian:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        try
        {
            f(n, x); // some function which, now being a C++ function, may 
                     // throw today or in the future (maybe someone used
                     // std::vector inside of it).
        }
        catch (...)
        {
            free(x);
            throw;
        }
        ...
        free(x);
        return success;
    }
    return fail;
}

Kotor! Itulah sebabnya sebagian besar pengembang C ++ akan menuntut ini:

void some_func(int n, ...)
{
    vector<int> x(n);
    f(x); // some function which, now being a C++ function, may throw today
          // or in the future.
}

Di atas adalah kode yang sesuai dengan pengecualian-aman RAII dari jenis yang umumnya disetujui pengembang C ++ karena fungsi tidak akan bocor pada baris kode mana yang memicu keluar tersirat sebagai akibat dari a throw.

Pilih Bahasa

Anda harus merangkul sistem tipe C ++ dan filosofi dengan RAII, pengecualian-keselamatan, templat, OOP, dll. Atau merangkul C yang sebagian besar berputar di sekitar bit dan byte mentah. Anda seharusnya tidak membentuk perkawinan yang tidak suci antara kedua bahasa ini, dan sebaliknya memisahkan mereka ke dalam bahasa yang berbeda untuk diperlakukan dengan sangat berbeda alih-alih mengaburkan keduanya.

Bahasa-bahasa ini ingin menikahi Anda. Anda biasanya harus memilih satu daripada berkencan dan bermain-main dengan keduanya. Atau Anda bisa menjadi poligami seperti saya dan menikahi keduanya, tetapi Anda harus sepenuhnya mengubah pemikiran Anda ketika menghabiskan waktu dengan satu sama lain dan menjaga mereka terpisah satu sama lain sehingga mereka tidak saling bertarung.

Ukuran Biner

Hanya karena penasaran saya mencoba mengambil implementasi daftar bebas saya dan patokan sekarang dan porting ke C ++ karena saya benar-benar ingin tahu tentang ini:

[...] tidak tahu seperti apa bentuk C karena saya belum pernah menggunakan kompiler C.

... dan ingin tahu apakah ukuran biner akan mengembang sama sekali hanya sebagai C ++. Itu mengharuskan saya untuk menaburkan gips eksplisit di seluruh tempat yang jelek (salah satu alasan saya suka benar-benar menulis hal-hal tingkat rendah seperti pengalokasi dan struktur data dalam C lebih baik) tetapi hanya butuh satu menit.

Ini hanya membandingkan rilis rilis MSVC 64-bit untuk aplikasi konsol sederhana dan dengan kode yang tidak menggunakan fitur C ++, bahkan operator tidak kelebihan - hanya perbedaan antara membangunnya dengan C dan menggunakan, katakanlah, <cstdlib>alih-alih <stdlib.h>dan hal-hal seperti itu, tetapi saya terkejut menemukan itu membuat perbedaan nol dengan ukuran biner!

Biner adalah 9,728byte ketika dibangun di C, dan juga 9,278byte saat dikompilasi sebagai kode C ++. Saya sebenarnya tidak mengharapkan itu. Saya pikir hal-hal seperti EH setidaknya akan menambah sedikit di sana (pikir itu setidaknya akan seperti seratus byte yang berbeda), meskipun mungkin itu bisa mengetahui bahwa tidak perlu menambahkan instruksi yang berhubungan dengan EH karena saya hanya menggunakan perpustakaan standar C dan tidak ada lemparan. Saya memikirkan sesuatuakan menambahkan sedikit ke ukuran biner bagaimanapun, seperti RTTI. Bagaimanapun, itu agak keren untuk melihat itu. Tentu saja saya tidak berpikir Anda harus menggeneralisasi dari hasil yang satu ini, tetapi setidaknya sedikit terkesan. Itu juga tidak berdampak pada tolok ukur, dan tentu saja, karena saya membayangkan ukuran biner yang identik juga berarti instruksi mesin yang dihasilkan identik.

Yang mengatakan, siapa yang peduli tentang ukuran biner dengan masalah keselamatan dan teknik yang disebutkan di atas? Jadi sekali lagi, pilih bahasa dan raih filosofinya alih-alih mencoba untuk mengastastasinya; itu yang saya rekomendasikan.


sumber