C++ ve Java'ya Derinlemesine Bir Bakış

Yayınlanan: 2022-07-22

Sayısız makale C++ ve Java'nın teknik özelliklerini karşılaştırıyor, ancak dikkate alınması gereken en önemli farklar hangileri? Örneğin, bir karşılaştırma Java'nın çoklu kalıtımı desteklemediğini ve C++'ın desteklediğini gösterdiğinde, bu ne anlama gelir? Ve bu iyi bir şey mi? Bazıları bunun Java'nın bir avantajı olduğunu iddia ederken, diğerleri bunu bir sorun olarak ilan ediyor.

Geliştiricilerin C++, Java veya başka bir dili birlikte seçmesi gereken durumları ve daha da önemlisi kararın neden önemli olduğunu keşfedelim.

Temelleri İncelemek: Dil Yapıları ve Ekosistemler

C++, TypeScript'in JavaScript'i derlemesine benzer şekilde, C derleyicilerinin ön ucu olarak 1985'te piyasaya sürüldü. Modern C++ derleyicileri tipik olarak yerel makine kodunu derler. Bazıları C++ derleyicilerinin taşınabilirliğini azalttığını ve yeni hedef mimariler için yeniden oluşturmayı gerektirdiğini iddia etse de, C++ kodu hemen hemen her işlemci platformunda çalışır.

İlk olarak 1995'te piyasaya sürülen Java, doğrudan yerel koda derleme yapmaz. Bunun yerine Java, Java Sanal Makinesi (JVM) üzerinde çalışan bir ara ikili gösterim olan bayt kodunu oluşturur. Başka bir deyişle, Java derleyicisinin çıktısının çalışması için platforma özgü yerel bir yürütülebilir dosyaya ihtiyacı vardır.

Hem C++ hem de Java, sözdizimlerinde genellikle C'ye benzedikleri için C benzeri diller ailesine girer. En önemli fark ekosistemleridir: C++, C veya C++'a veya bir işletim sisteminin API'sine dayalı kitaplıkları sorunsuz bir şekilde çağırabilirken, Java en çok Java tabanlı kitaplıklar için uygundur. Java Yerel Arabirimi (JNI) API'sini kullanarak Java'daki C kitaplıklarına erişebilirsiniz, ancak bu hataya açıktır ve bazı C veya C++ kodları gerektirir. C++ ayrıca donanımla Java'dan daha kolay etkileşime girer, çünkü C++ daha düşük seviyeli bir dildir.

Ayrıntılı Dengeler: Jenerikler, Bellek ve Daha Fazlası

C++ ile Java'yı birçok açıdan karşılaştırabiliriz. Bazı durumlarda, C++ ve Java arasındaki karar açıktır. Uygulama bir oyun değilse, yerel Android uygulamaları genellikle Java kullanmalıdır. Çoğu oyun geliştiricisi, mümkün olan en akıcı gerçek zamanlı animasyon için C++ veya başka bir dili tercih etmelidir; Java'nın bellek yönetimi genellikle oyun sırasında gecikmeye neden olur.

Oyun olmayan çapraz platform uygulamaları bu tartışmanın kapsamı dışındadır. Bu durumda ne C++ ne de Java ideal değildir çünkü verimli GUI geliştirme için fazla ayrıntılıdırlar. Yüksek performanslı uygulamalar için, ağır işleri yapmak için C++ modülleri oluşturmak ve GUI için geliştirici açısından daha üretken bir dil kullanmak en iyisidir.

Oyun olmayan çapraz platform uygulamaları bu tartışmanın kapsamı dışındadır. Bu durumda ne C++ ne de Java ideal değildir çünkü verimli GUI geliştirme için fazla ayrıntılıdırlar.

Cıvıldamak

Bazı projeler için seçim net olmayabilir, o yüzden biraz daha karşılaştıralım:

Özellik C++ Java
Acemi dostu Numara Evet
Çalışma zamanı performansı En iyi İyi
gecikme tahmin edilebilir Öngörülemeyen
Referans sayan akıllı işaretçiler Evet Numara
Küresel işaretle ve süpür çöp toplama Numara Gerekli
Yığın bellek ayırma Evet Numara
Yerel yürütülebilir dosyaya derleme Evet Numara
Java bayt koduna derleme Numara Evet
Düşük seviyeli işletim sistemi API'leri ile doğrudan etkileşim Evet C kodu gerektirir
C kitaplıkları ile doğrudan etkileşim Evet C kodu gerektirir
Java kitaplıkları ile doğrudan etkileşim JNI aracılığıyla Evet
Standartlaştırılmış yapı ve paket yönetimi Numara Uzman


Tabloda karşılaştırılan özelliklerin yanı sıra, çoklu kalıtım, jenerikler/şablonlar ve yansıma gibi nesne yönelimli programlama (OOP) özelliklerine de odaklanacağız. Her iki dilin de OOP'yi desteklediğini unutmayın: Java bunu zorunlu kılarken, C++ global işlevler ve statik verilerle birlikte OOP'yi destekler.

Çoklu Kalıtım

OOP'de kalıtım, bir alt sınıfın bir üst sınıftan öznitelikleri ve yöntemleri devralmasıdır. Standart bir örnek, daha genel bir Shape sınıfından miras alan bir Rectangle sınıfıdır:

 // Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };

Çoklu kalıtım, bir alt sınıfın birden fazla ebeveynden miras almasıdır. Rectangle ve Shape sınıflarını ve ek bir Clickable sınıfı kullanan bir örnek:

 // Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };

Bu durumda iki temel türümüz vardır: Shape ( Rectangle temel türü) ve Clickable . ClickableRectangle , iki nesne türünü oluşturmak için her ikisinden de devralır.

C++ çoklu kalıtımı destekler; Java'da yok. Çoklu kalıtım, aşağıdakiler gibi belirli uç durumlarda yararlıdır:

  • Gelişmiş bir etki alanına özgü dil (DSL) oluşturma.
  • Derleme zamanında karmaşık hesaplamalar yapmak.
  • Java'da mümkün olmayan yollarla proje tipi güvenliğinin iyileştirilmesi.

Ancak, çoklu kalıtımın kullanılması genellikle önerilmez. En iyi yalnızca en deneyimli C++ programcıları tarafından yapılan bir şey olan şablon metaprogramlama ile birleştirilmedikçe, kodu karmaşıklaştırabilir ve performansı etkileyebilir.

Jenerikler ve Şablonlar

Herhangi bir veri türüyle çalışan sınıfların genel sürümleri, kodun yeniden kullanımı için pratiktir. Her iki dil de bu desteği sunar—jenerikler aracılığıyla Java, şablonlar aracılığıyla C++—ancak C++ şablonlarının esnekliği, gelişmiş programlamayı daha güvenli ve daha sağlam hale getirebilir. C++ derleyicileri, şablonla farklı türleri her kullandığınızda yeni özelleştirilmiş sınıflar veya işlevler oluşturur. Ayrıca, C++ şablonları, belirli veri türlerinin özel koda sahip olmasına izin vererek, üst düzey işlevin parametre türlerine dayalı olarak özel işlevleri çağırabilir. Buna şablon uzmanlığı denir. Java'nın eşdeğer bir özelliği yoktur.

Buna karşılık, jenerikleri kullanırken, Java derleyicileri, tür silme adı verilen bir işlem aracılığıyla türleri olmayan genel nesneler oluşturur. Java, derleme sırasında tür denetimi gerçekleştirir, ancak programcılar, tür parametrelerine dayalı olarak genel bir sınıfın veya yöntemin davranışını değiştiremez. Bunu daha iyi anlamak için, bir C++ şablonundan template, template<class T1, class T2> kullanan genel bir std::string format(std::string fmt, T1 item1, T2 item2) işlevinin hızlı bir örneğine bakalım. oluşturduğum kitaplık:

 std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);

C++, format işlevini std::string format(std::string fmt, std::string item1, int item2) olarak üretirken, Java onu item1 ve item2 için belirli string ve int nesne türleri olmadan yaratır. Bu durumda, C++ şablonumuz son gelen parametrenin bir int olduğunu bilir ve bu nedenle ikinci format çağrısında gerekli std::to_string dönüşümünü gerçekleştirebilir. Şablonlar olmadan, ikinci format çağrısında olduğu gibi bir sayıyı dize olarak yazdırmaya çalışan bir C++ printf ifadesi tanımsız davranışa sahip olabilir ve uygulamayı çökertebilir veya çöp yazdırabilir. Java işlevi, ilk format çağrısında bir sayıyı yalnızca bir dize olarak ele alabilir ve onu doğrudan onaltılık bir tamsayı olarak biçimlendirmez. Bu önemsiz bir örnektir, ancak C++'ın sınıfını veya format işlevini değiştirmeden herhangi bir rastgele sınıf nesnesini işlemek için özel bir şablon seçme yeteneğini gösterir. Bu yöntem daha az genişletilebilir ve daha fazla hataya açık olsa da, Java'da jenerikler yerine yansıma kullanarak çıktıyı doğru şekilde üretebiliriz.

Refleks

Java'da, bir sınıf veya sınıf türünde hangi üyelerin mevcut olduğu gibi yapısal ayrıntıları (çalışma zamanında) bulmak mümkündür. Bu özelliğe yansıma denir, çünkü muhtemelen içinde ne olduğunu görmek için nesneye bir ayna tutmak gibidir. (Daha fazla bilgi Oracle'ın yansıma belgelerinde bulunabilir.)

C++ tam yansımaya sahip değildir, ancak modern C++, çalışma zamanı türü bilgileri (RTTI) sunar. RTTI, belirli nesne türlerinin çalışma zamanı algılamasına izin verir, ancak nesnenin üyeleri gibi bilgilere erişemez.

Hafıza yönetimi

C++ ve Java arasındaki bir diğer kritik fark, iki ana yaklaşımı olan bellek yönetimidir: geliştiricilerin belleği manuel olarak takip edip serbest bırakması gereken manuel; ve otomatik, yazılımın kullanılmayan belleği geri dönüştürmek için hangi nesnelerin hala kullanımda olduğunu izlediği. Java'da bir örnek çöp toplamadır.

Java, manuel yaklaşıma göre daha kolay bellek yönetimi sağlayan ve genellikle güvenlik açıklarına katkıda bulunan bellek serbest bırakan hataları ortadan kaldıran çöpten toplanmış belleğe ihtiyaç duyar. C++ yerel olarak otomatik bellek yönetimi sağlamaz, ancak akıllı işaretçiler adı verilen bir tür çöp toplamayı destekler. Akıllı işaretçiler, referans sayımını kullanır ve doğru kullanıldığında güvenli ve performanslıdır. C++ ayrıca bir nesnenin yok edilmesi üzerine kaynakları temizleyen veya serbest bırakan yıkıcılar da sunar.

Java yalnızca yığın ayırma sunarken, C++ hem yığın ayırmayı ( new ve delete ya da eski C malloc işlevlerini kullanarak) hem de yığın ayırmayı destekler. Yığın ayırma, yığın ayırmadan daha hızlı ve daha güvenli olabilir, çünkü yığın doğrusal bir veri yapısıyken yığın ağaç tabanlıdır, bu nedenle yığın belleğinin ayrılması ve serbest bırakılması çok daha kolaydır.

C++'ın yığın tahsisiyle ilgili diğer bir avantajı, Kaynak Edinme Başlatmadır (RAII) olarak bilinen bir programlama tekniğidir. RAII'de, referanslar gibi kaynaklar, kontrol nesnelerinin yaşam döngüsüne bağlanır; kaynaklar, o nesnenin yaşam döngüsünün sonunda yok edilecektir. RAII, C++ akıllı işaretçilerinin manuel referans kaldırma olmadan nasıl çalıştığıdır; bir fonksiyonun en üstünde referans verilen bir akıllı işaretçi, fonksiyondan çıktıktan sonra otomatik olarak referanstan çıkarılır. Akıllı işaretçiye yapılan son referans ise, bağlı bellek de serbest bırakılır. Java benzer bir model sunsa da, özellikle aynı kod bloğunda birkaç kaynak oluşturmanız gerekiyorsa, C++'ın RAII'sinden daha garip.

Çalışma Zamanı Performansı

Java'nın sağlam çalışma zamanı performansı vardır, ancak manuel bellek yönetimi gerçek dünya uygulamaları için çöp toplamadan daha hızlı olduğu için C++ hala tacı tutar. Java, JIT derlemesi nedeniyle bazı köşe durumlarda C++'tan daha iyi performans gösterebilse de, C++ önemsiz olmayan çoğu durumda kazanır.

Özellikle, Java'nın standart bellek kitaplığı, C++'ın azaltılmış yığın ayırma kullanımına kıyasla, ayırmalarıyla çöp toplayıcıyı fazla çalıştırır. Bununla birlikte, Java hala nispeten hızlıdır ve örneğin gerçek zamanlı kısıtlamaları olan oyunlarda veya uygulamalarda gecikme en önemli sorun olmadığı sürece kabul edilebilir olmalıdır.

Yapı ve Paket Yönetimi

Java'nın performans eksikliğini, kullanım kolaylığını telafi eder. Geliştirici verimliliğini etkileyen bileşenlerden biri, projeleri nasıl oluşturduğumuz ve bir uygulamaya dış bağımlılıkları nasıl getirdiğimiz olan derleme ve paket yönetimidir. Java'da Maven adlı bir araç, bu işlemi birkaç kolay adımda basitleştirir ve IntelliJ IDEA gibi birçok IDE ile bütünleşir.

Ancak C++'da standart bir paket deposu yoktur. Uygulamalarda C++ kodu oluşturmak için standartlaştırılmış bir yöntem bile yoktur: Bazı geliştiriciler Visual Studio'yu tercih ederken, diğerleri CMake veya başka bir özel araç seti kullanır. Karmaşıklığa ek olarak, bazı ticari C++ kitaplıkları ikili biçimlidir ve bu kitaplıkları derleme sürecine entegre etmenin tutarlı bir yolu yoktur. Ayrıca, yapı ayarlarındaki veya derleyici sürümlerindeki farklılıklar, ikili kitaplıkların çalışmasını sağlamada zorluklara neden olabilir.

Başlangıç ​​dostu

Derleme ve paket yönetimi sürtüşmeleri, C++'ın Java'dan çok daha az başlangıç ​​dostu olmasının tek nedeni değildir. Bir programcı, C'ye, montaj dillerine veya bir bilgisayarın alt düzey çalışmalarına aşina olmadığı sürece, hata ayıklama ve C++'ı güvenli bir şekilde kullanma konusunda zorluk yaşayabilir. C++'ı bir elektrikli alet gibi düşünün: Çok şey başarabilir ancak yanlış kullanıldığında tehlikelidir.

Java'nın yukarıda bahsedilen bellek yönetimi yaklaşımı, onu C++'dan çok daha erişilebilir kılar. Java programcılarının nesne belleğini serbest bırakma konusunda endişelenmelerine gerek yoktur, çünkü dil bunu otomatik olarak halleder.

Karar Zamanı: C++ mı Java mı?

Sol üst köşede koyu mavi bir "Başlat" baloncuğu bulunan ve sonunda "Evet" ve diğer seçenekler için koyu mavi dalları olan bir dizi beyaz karar kavşağı aracılığıyla altındaki yedi açık mavi sonuç kutusundan birine bağlanan bir akış şeması, ve "Hayır" için açık mavi dallar. Birincisi "Çapraz platform GUI uygulaması?" "Evet", "Bir platformlar arası geliştirme ortamı seçin ve birincil dilini kullanın" sonucuna işaret eder. "Hayır", "Yerel Android Uygulaması?" "Evet", ikincil bir soruya işaret eder, "Bu bir oyun mu?" İkincil sorudan, "Hayır", "Java (veya Kotlin kullanın)" sonucuna, "Evet" ise farklı bir sonuca işaret eder, "Bir platformlar arası oyun motoru seçin ve önerilen dilini kullanın." "Yerel Android Uygulamasından?" "Hayır", "Yerel Windows uygulaması?" "Evet", ikincil bir soruya işaret eder, "Bu bir oyun mu?" İkincil sorudan "Evet", "Bir platformlar arası oyun motoru seçin ve önerilen dilini kullanın" sonucuna işaret eder ve "Hayır", farklı bir sonuca işaret eder, "Bir Windows GUI ortamı seçin ve birincil dil (tipik olarak C++ veya C#)." "Yerel Windows uygulamasından?" "Hayır", "Sunucu uygulaması?" "Evet", ikincil bir soruya işaret eder, "Geliştirici türü?" İkincil sorudan, "Orta düzey" bir karar "Java (veya C# veya TypeScript kullanın)" sonucuna işaret eder ve "Yetenekli" bir karar üçüncül bir soruya işaret eder, "Birinci öncelik?" Üçüncü sorudan, "Geliştirici üretkenliği" kararı "Java (veya C# veya TypeScript kullanın)" sonucuna işaret eder ve "Performans" kararı farklı bir sonuca işaret eder, "C++ (veya Rust) kullanın". "Sunucu uygulamasından?" sorusu, "Hayır" ikincil bir soruya işaret eder, "Sürücü geliştirme?" İkincil sorudan, "Evet" bir sonuca, "C++ (veya Rust kullan)"a ve "Hayır", üçüncül soruya, "IoT geliştirme?" Üçüncül sorudan "Evet", "C++ (veya Rust) Kullanın" sonucuna, "Hayır" ise dördüncül soruya, "Yüksek hızlı ticaret?" Dördüncü sorudan, "Evet", "C++ (veya Rust Kullan)" sonucuna, "Hayır" ise kalan son sonuca, "Hedef etki alanınızı bilen birine sorun" sonucuna işaret eder.
Çeşitli proje türleri için en iyi dili seçmeye yönelik genişletilmiş bir kılavuz.

C++ ve Java arasındaki farkları derinlemesine araştırdığımıza göre, asıl sorumuza geri dönüyoruz: C++ mı Java mı? İki dili derinlemesine anlamış olsanız bile, herkese uyan tek bir cevap yoktur.

Düşük seviyeli programlama kavramlarına aşina olmayan yazılım mühendisleri, oyun gibi gerçek zamanlı bağlamlar dışında, kararı C++ veya Java ile sınırlarken Java'yı seçmekten daha iyi olabilir. Ufuklarını genişletmek isteyen geliştiriciler ise C++'ı seçerek daha fazlasını öğrenebilirler.

Ancak, C++ ve Java arasındaki teknik farklar, kararda yalnızca küçük bir faktör olabilir. Bazı ürün türleri özel seçimler gerektirir. Hala emin değilseniz, akış şemasına başvurabilirsiniz - ancak bunun sizi eninde sonunda üçüncü bir dile yönlendirebileceğini unutmayın.