Saya percaya saya telah mencampur kode C dan C ++ ketika saya seharusnya tidak melakukannya; Apakah ini masalah dan bagaimana cara memperbaikinya?


10

Latar Belakang / Skenario

Saya mulai menulis aplikasi CLI murni dalam C (program C atau C ++ pertama saya yang tepat yang bukan "Hello World" atau variasi daripadanya). Sekitar pertengahan saya bekerja dengan "string" input pengguna (array char) dan saya menemukan objek streamer string C ++. Saya melihat bahwa saya dapat menyimpan kode menggunakan ini, jadi saya menggunakannya melalui aplikasi. Ini berarti bahwa saya telah mengubah ekstensi file menjadi .cpp dan sekarang kompilasi aplikasi dengan g++bukan gcc. Jadi berdasarkan ini, saya akan mengatakan aplikasi ini sekarang secara teknis aplikasi C ++ (walaupun 90% + dari kode ditulis dalam apa yang saya sebut C, karena ada banyak persilangan antara dua bahasa mengingat pengalaman saya yang terbatas keduanya). Ini adalah file .cpp tunggal dengan panjang sekitar 900 baris.

Faktor Penting

Saya ingin program ini gratis (seperti dalam uang) dan dapat didistribusikan secara bebas dan dapat digunakan semua orang. Kekhawatiran saya adalah bahwa seseorang akan melihat kode dan memikirkan sesuatu untuk efek:

Oh, lihat kodenya, mengerikan, program ini tidak bisa membantu saya

Kapan berpotensi itu bisa! Masalah lainnya adalah kode menjadi efisien (ini adalah program untuk menguji konektivitas Ethernet). Seharusnya tidak ada bagian dari kode yang sangat tidak efisien sehingga dapat sangat menghambat kinerja aplikasi atau outputnya. Namun, saya pikir itu adalah pertanyaan untuk Stack Overflow ketika meminta bantuan dengan fungsi, metode, panggilan objek tertentu, dll.

Pertanyaan saya

Memiliki (menurut saya) campuran C dan C ++ di mana mungkin saya tidak boleh. Haruskah saya melihat untuk menulis ulang semuanya dalam C ++ (dengan ini, maksud saya mengimplementasikan lebih banyak objek C ++ dan metode di mana mungkin saya telah mengkodekan sesuatu dalam gaya C yang dapat dikondensasi menggunakan teknik C ++ yang lebih baru), atau menghapus penggunaan objek string streamer dan bawa semuanya "kembali" ke kode C? Apakah ada pendekatan yang benar di sini? Saya tersesat dan memerlukan beberapa panduan tentang cara menjaga aplikasi ini "Baik" di mata massa, sehingga mereka akan menggunakannya dan mendapat manfaat darinya.

Kode - Pembaruan

Berikut ini tautan ke kode tersebut. Sekitar 40% komentar, saya berkomentar hampir setiap baris sampai saya merasa lebih lancar. Dalam salinan yang saya tautkan, saya telah menghapus hampir semua komentar. Saya harap ini tidak membuatnya terlalu sulit untuk dibaca. Namun saya berharap tidak ada yang perlu memahaminya sepenuhnya. Jika saya telah membuat kesalahan desain yang fatal, saya berharap itu dapat diidentifikasi dengan mudah. Saya juga harus menyebutkan, saya menulis beberapa desktop dan laptop Ubuntu. Saya tidak bermaksud untuk porting kode ke sistem operasi lain.


3
Saya ambigu CLI untuk Anda. CLI juga dapat merujuk ke Infrastruktur Bahasa Umum, yang tidak masuk akal dari perspektif C.
Robert Harvey

Anda bisa menulis ulang di FORTRAN. Saya tidak pernah mendengar OOF.
ott--

2
Saya tidak akan terlalu khawatir tentang orang-orang yang tidak menggunakan perangkat lunak Anda jika tidak "cantik". 99% dari pengguna Anda bahkan tidak akan melihat kode atau peduli tentang bagaimana itu ditulis selama itu memenuhi apa yang mereka butuhkan untuk dilakukan. Namun, penting agar kode konsisten, dll. Untuk membantu Anda mempertahankannya dalam jangka panjang.
Evicatos

1
Mengapa Anda tidak membuat perangkat lunak bebas milik Anda (mis. Di bawah lisensi GPLv3 ), dengan kode pada mis . Github . Kode Anda kurang LICENSEfile. Anda mungkin mendapatkan umpan balik yang menarik.
Basile Starynkevitch

Jawaban:


13

Mari kita mulai dari awal: kode campuran C dan C ++ cukup umum. Jadi Anda berada di klub besar untuk memulai. Kami memiliki basis kode C besar di alam. Tetapi untuk alasan yang jelas banyak programmer menolak untuk menulis setidaknya hal-hal baru dalam C, memiliki akses ke C ++ dalam kompiler yang sama, modul-modul baru mulai ditulis seperti itu - pada awalnya hanya meninggalkan bagian-bagian yang ada sendirian.

Kemudian pada akhirnya beberapa file yang ada dikompilasi ulang sebagai C ++, dan beberapa jembatan dapat dihapus ... Tetapi mungkin butuh waktu sangat lama.

Anda agak maju, sistem lengkap Anda sekarang C ++, hanya sebagian besar ditulis "C-style". Dan Anda melihat campuran gaya merupakan masalah, apa yang seharusnya tidak Anda lakukan: C ++ adalah bahasa multi-paradigma yang mendukung banyak gaya, dan memungkinkan mereka untuk hidup berdampingan dengan baik. Sebenarnya itu adalah kekuatan utama, bahwa Anda tidak dipaksa untuk gaya tunggal. Satu yang akan menjadi suboptimal di sana-sini, dengan sedikit keberuntungan tidak di mana-mana.

Bekerja kembali basis kode adalah ide yang baik, JIKA itu rusak. Atau jika itu di jalan pembangunan. Tetapi jika itu berhasil (dalam pengertian asli), silakan ikuti prinsip rekayasa paling dasar: jika tidak rusak, jangan memperbaikinya. Biarkan bagian-bagian dingin sendirian, taruh usaha Anda di tempat yang penting. Pada bagian yang buruk, berbahaya - atau dalam fitur baru, dan hanya memperbaiki bagian untuk membuat mereka menjadi tempat tidur.

Jika Anda mencari hal-hal umum untuk diatasi, inilah yang layak diusir dari basis kode C:

  • semua fungsi str * dan char [] - gantikan dengan kelas string
  • jika Anda menggunakan sprintf, buat versi yang mengembalikan string dengan hasilnya, atau masukkan ke dalam string, dan ganti penggunaan. (Jika Anda tidak pernah terganggu dengan stream, bantulah diri Anda sendiri dan lewati saja, kecuali jika Anda menyukainya; gcc memberikan keamanan tipe yang sempurna di luar kotak untuk memeriksa format, cukup tambahkan atribut yang tepat.
  • kebanyakan malloc dan gratis - BUKAN dengan yang baru dan hapus, tetapi vektor, daftar, peta, dan kolega lainnya.
  • sisa manajemen memori (setelah dua poin sebelumnya itu harus cukup langka, tutup dengan pointer pintar atau mengimplementasikan koleksi khusus Anda
  • ganti semua penggunaan sumber daya lainnya (FILE *, mutex, kunci, dll) untuk menggunakan pembungkus atau kelas RAII

Ketika Anda selesai dengan itu, Anda mendekati titik di mana basis kode bisa cukup aman-pengecualian, sehingga Anda bisa menjatuhkan sepakbola kode-kembali menggunakan pengecualian dan jarang mencoba / menangkap hanya di fungsi tingkat tinggi.

Di luar itu, cukup tulis kode baru di beberapa C ++ sehat, dan jika beberapa kelas lahir yang merupakan pengganti yang baik dalam kode yang ada, ambil mereka.

Saya tidak menyebutkan hal-hal yang berhubungan dengan sintaks, jelas menggunakan ref bukan pointer dalam semua kode baru, tetapi mengganti bagian C lama hanya untuk perubahan itu bukan nilai yang baik. Cast Anda harus mengatasi, menghilangkan semua yang Anda bisa, dan menggunakan varian C ++ dalam fungsi wrapper untuk sisanya. Dan yang sangat penting, tambahkan const di mana pun berlaku. Ini interleave dengan peluru sebelumnya. Dan konsolidasi makro Anda, dan ganti apa yang bisa Anda buat menjadi enum, fungsi inline, atau templat.

Saya sarankan membaca Sutter C ++ Standard Sutter / Alexandrescu jika belum dilakukan dan ikuti mereka dengan seksama.


Terima kasih banyak atas masukan Balog, semua saran yang masuk akal di mata saya dan saya setuju dengan semua itu. Saya akan melihat perlahan-lahan mengubah bagian kode menurut bagian saya pikir, memprioritaskan kode kerja.
jwbensley

7

Singkatnya: Anda tidak akan ke neraka, tidak ada hal buruk yang akan terjadi, tetapi Anda juga tidak akan memenangkan kompetisi kecantikan.

Meskipun sangat mungkin untuk menggunakan C ++ sebagai "C lebih baik", Anda membuang banyak manfaat C ++, tetapi karena Anda tidak membatasi diri untuk vanilla C, Anda tidak mendapatkan manfaat C (kesederhanaan, portabilitas) , transparansi, interoperabilitas). Dengan kata lain: C ++ mengorbankan beberapa kualitas C untuk mendapatkan yang lain - misalnya, transparansi C di mana Anda selalu dapat melihat dengan jelas di mana dan kapan alokasi memori terjadi diperdagangkan untuk abstraksi C ++ yang lebih kuat.

Karena kode Anda tampaknya berfungsi dengan baik sekarang, mungkin bukan ide yang baik untuk menulis ulang hanya untuk kepentingan itu: pertahankan sebagaimana adanya untuk saat ini, dan ubah ke C ++ yang lebih idiomatis saat Anda menggunakannya, satu per satu, setiap kali Anda mengerjakan bagian yang diberikan. Dan simpan pelajaran yang dipelajari dalam pikiran untuk proyek Anda berikutnya, sebagian besar dari semua ini: C dan C ++ bukan bahasa yang sama, dan Anda lebih baik membuat pilihan di muka daripada memutuskan untuk beralih ke C ++ setengah jalan melalui proyek Anda .


Terima kasih tdammers atas sarannya. Saya setuju dengan apa yang Anda katakan dan saya menerimanya, terima kasih!
jwbensley

4

C ++ adalah bahasa yang sangat kompleks, dalam arti ia memiliki banyak fitur. Ini juga merupakan bahasa yang mendukung banyak paradigma, artinya memungkinkan Anda untuk menulis kode prosedural sepenuhnya tanpa menggunakan objek apa pun.

Jadi karena C ++ sangat kompleks, Anda sering melihat orang menggunakan beberapa subset terbatas dari fitur-fiturnya dalam kode mereka. Pemula sering hanya menggunakan aliran I / O, objek string, dan baru / hapus alih-alih malloc / gratis, dan mungkin referensi bukan pointer. Ketika Anda belajar tentang fitur-fitur berorientasi objek, Anda dapat mulai menulis dalam gaya yang disebut "C dengan kelas". Akhirnya, saat Anda mempelajari lebih lanjut tentang C ++, Anda mulai menggunakan templat, RAII , STL , smart pointer, dll.

Poin yang saya coba buat adalah bahwa belajar C ++ adalah proses yang membutuhkan waktu. Ya, sekarang kode Anda mungkin terlihat seperti ditulis oleh seorang programmer C yang mencoba menulis C ++. Dan karena Anda baru belajar, itu tidak masalah. Namun, itu membuat saya ngeri, ketika saya melihat hal semacam ini dalam kode produksi yang ditulis oleh programmer berpengalaman yang seharusnya tahu lebih baik.

Ingat saja, programmer Fortran yang baik dapat menulis kode Fortran yang baik dalam bahasa apa pun. :)


2

Kedengarannya Anda merekap ulang persis jalan yang diambil oleh banyak programmer C lama ketika pergi ke C ++. Anda menggunakannya sebagai "C lebih baik". Dua puluh tahun yang lalu ini masuk akal, tetapi pada titik ini ada begitu banyak konstruksi yang lebih kuat di C ++ (seperti Perpustakaan Template Standar) yang jarang masuk akal sekarang. Minimal, Anda mungkin harus melakukan yang berikut untuk menghindari memberikan programer C ++ aneurysms saat melihat kode Anda:

  • Gunakan stream bukan? printfkeluarga.
  • Gunakan newsebagai ganti malloc.
  • Gunakan kelas wadah STL untuk semua struktur data.
  • Gunakan std::stringalih-alih char*sedapat mungkin
  • Memahami dan menggunakan RAII

Jika program Anda pendek (dan ~ 900 baris sepertinya pendek) Saya pribadi tidak berpikir mencoba membuat kelas yang kuat diperlukan atau bahkan berguna.


Terima kasih atas saran Steven, Ya saya punya ide yang sama tentang kelas dan saya pikir saya bisa merapikan kode menjadi satu file tunggal yang rapi. Terima kasih!
jwbensley

2

Jawaban yang diterima hanya menyebutkan kelebihan konversi C ke idiomatik C ++, seolah-olah kode C ++ akan lebih baik daripada kode C. Saya setuju dengan jawaban lain bahwa mungkin tidak perlu melakukan perubahan drastis pada kode campuran jika tidak rusak.

Apakah pencampuran C dan C ++ berkelanjutan tergantung pada bagaimana pencampuran dilakukan. Bug halus dapat diperkenalkan misalnya ketika pengecualian dimunculkan dalam kode-C (yang tidak terkecuali aman), menyebabkan kebocoran memori atau kerusakan data. Cara yang lebih aman dan cukup umum adalah dengan membungkus C-libraries atau interface di kelas, atau menggunakannya dalam beberapa jenis isolasi lain dalam proyek C ++.

Sebelum Anda memutuskan untuk menulis ulang seluruh proyek Anda menjadi C ++ (atau C) idiomatik, Anda harus menyadari bahwa banyak perubahan yang disajikan dalam jawaban lain dapat membuat program Anda lebih lambat, atau menyebabkan efek yang tidak diinginkan lainnya. Sebagai contoh:

  • mengubah stack-dialokasikan C-string ke std :: string dapat menyebabkan alokasi tumpukan yang tidak perlu
  • mengubah pointer mentah ke beberapa tipe pointer bersama (seperti std :: shared_ptr) menyebabkan overhead akses karena penghitungan referensi, fungsi anggota virtual internal dan keamanan utas
  • stream perpustakaan standar lebih lambat dari rekan-rekan C
  • penggunaan kelas RAII yang ceroboh, seperti wadah, dapat menyebabkan operasi yang tidak perlu, terutama ketika memindahkan semantik C ++ 11 tidak dapat digunakan
  • templat dapat menyebabkan waktu kompilasi lebih lama, kesalahan kompilasi tidak jelas, masalah kode dan portabilitas masalah antar kompiler
  • menulis kode pengecualian-aman adalah sulit, yang membuat pengenalan bug halus selama konversi menjadi mudah
  • lebih mudah menggunakan kode C ++ dari pustaka bersama daripada C biasa
  • C ++ tidak didukung secara luas seperti misalnya C89

Untuk menyimpulkan, mengubah dari kode C dan C ++ campuran ke idiomatik C ++ harus dilihat sebagai tradeoff yang memiliki lebih banyak daripada hanya waktu implementasi dan memperbesar kotak peralatan Anda dengan fitur yang tampaknya nyaman. Karena itu sangat sulit untuk memberikan jawaban untuk kasus umum selain "itu tergantung".


"stream perpustakaan standar lebih lambat daripada rekan-rekan C": Kemungkinan, tentu saja lebih sulit untuk internasionalisasi daripada printf.
Deduplicator

1

Ini adalah bentuk yang buruk untuk mencampur C dan C ++. Mereka adalah bahasa yang berbeda dan benar-benar harus diperlakukan seperti itu. Pilih yang mana yang paling nyaman bagi Anda dan cobalah untuk menulis kode idiomatik dalam bahasa itu.

Jika C ++ menghemat banyak kode Anda, tetap gunakan C ++ dan tulis ulang bagian C. Saya ragu perbedaan kinerja bahkan akan terlihat, jika itu yang Anda khawatirkan.


Jadi saya punya satu pustaka yang ingin saya gunakan yang ditulis dalam C, dan saya punya pustaka lain yang ingin saya gunakan yang ditulis dalam C ++ ... Menulis ulang kode yang berfungsi dengan baik hanya membuat karya yang tidak perlu.
gnasher729

1

Saya bolak-balik antara C dan C ++ sepanjang waktu dan saya adalah tipe orang aneh yang lebih suka bekerja dengan cara ini. Saya lebih suka C ++ untuk hal-hal tingkat tinggi sementara saya menggunakan C sebagai instrumen tumpul yang memungkinkan saya untuk x-ray tipe data dan memperlakukan mereka seperti bit dan byte dengan, katakanlah, memcpyyang saya temukan berguna untuk menerapkan struktur data tingkat rendah dan pengalokasi memori . Jika Anda menemukan diri Anda bekerja pada level bit dan byte mentah, maka sistem tipe C ++ yang sangat kaya tidak membantu sama sekali, dan saya sering merasa lebih mudah untuk menulis kode seperti itu di C di mana saya dapat dengan aman berasumsi bahwa saya dapat memperlakukan semua tipe data C hanya bit dan byte.

Hal utama yang perlu diingat adalah di mana kedua bahasa ini tidak bertautan dengan baik.

1. Fungsi AC seharusnya tidak pernah memanggil fungsi C ++ yang bisa throw. Mengingat banyaknya tempat jenis kode C ++ harian Anda dapat secara implisit mengalami pengecualian, itu biasanya berarti fungsi C Anda seharusnya tidak memanggil fungsi C ++ secara umum. Kalau tidak, kode C Anda tidak akan dapat membebaskan sumber daya yang dialokasikannya selama pelepasan tumpukan karena tidak ada cara praktis untuk menangkap pengecualian C ++. Jika Anda benar-benar membutuhkan kode C Anda untuk memanggil fungsi C ++ sebagai panggilan balik, misalnya, maka sisi C ++ harus memastikan bahwa setiap pengecualian yang ditemukannya ditangkap sebelum kembali ke kode C.

2. Jika Anda menulis kode C yang memperlakukan tipe data hanya sebagai bit dan byte mentah, melibas sistem tipe (ini sebenarnya alasan utama saya untuk menggunakan C di tempat pertama), maka Anda tidak pernah ingin menggunakan kode tersebut terhadap data C ++ tipe yang dapat memiliki copy constructor dan destructor dan virtual pointer dan hal-hal semacam itu yang perlu dihormati. Jadi umumnya kode C harus menggunakan kode C semacam ini, bukan kode C ++ yang menggunakan kode C semacam ini.

Jika Anda ingin kode C ++ Anda menggunakan kode C seperti itu, maka biasanya Anda ingin menggunakannya sebagai detail implementasi kelas yang memastikan bahwa data yang disimpannya dalam struktur data C generik adalah tipe data yang sepele untuk dikonstruksi dan hancurkan. Untungnya ada tipe sifat seperti ini yang dapat Anda gunakan untuk memeriksa bahwa dalam pernyataan statis, memastikan bahwa tipe yang Anda simpan ke dalam struktur data C generik memiliki destruktor dan konstruktor yang sepele dan akan tetap seperti itu.

Haruskah saya melihat untuk menulis ulang semuanya dalam C ++ (dengan ini, maksud saya mengimplementasikan lebih banyak objek C ++ dan metode di mana mungkin saya telah mengkodekan sesuatu dalam gaya C yang dapat dikondensasi menggunakan teknik C ++ yang lebih baru), atau menghapus penggunaan objek string streamer dan bawa semuanya "kembali" ke kode C?

Menurut pendapat saya, Anda tidak perlu repot jika mematuhi aturan di atas dan memastikan kode Anda teruji dengan baik terhadap tes yang Anda tulis. Bagian yang mungkin perlu ditingkatkan adalah kode yang Anda tulis dalam C ++ sejauh ini, tetapi itu tidak berarti Anda harus mem-port kode yang ketat berbasis C ke C ++.

Dengan kode yang Anda tulis dalam C ++ dan kompilasi sebagai kode C ++, Anda harus hati-hati. Menggunakan C-like coding di C ++ meminta masalah. Misalnya, jika Anda mengalokasikan dan membebaskan sumber daya secara manual, kemungkinan kode Anda bukan pengecualian aman karena kode Anda mungkin menemukan pengecualian di titik mana Anda secara implisit keluar dari fungsi sebelum dapat freesumber daya tersebut. Di C ++, RAII dan hal-hal seperti smart pointer bukan kenyamanan yang halus karena mereka mungkin muncul jika Anda hanya melihat jalur eksekusi normal tanpa mempertimbangkan jalur luar biasa. Mereka seringkali merupakan kebutuhan mendasar untuk menulis kode pengecualian-aman yang benar secara sederhana dan efektif yang tidak hanya mulai bocor di semua tempat ketika menemukan pengecualian.

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.