C++ Eleştirisi - Criticism of C++

C++ , zorunlu , nesne yönelimli ve genel programlama özelliklerine sahip genel amaçlı bir programlama dilidir . Linus Torvalds , Richard Stallman , Joshua Bloch , Rob Pike , Ken Thompson ve Donald Knuth gibi tanınmış yazılım geliştiricileri tarafından C++'ın tasarımına birçok eleştiri yöneltildi .

C++, kapsamlı ancak tam olmayan, C ile geriye dönük uyumluluğu olan çok paradigmalı bir programlama dilidir . Bu makale, işaretçi aritmetiği , operatör önceliği veya önişlemci makroları gibi C özelliklerine değil, genellikle eleştirilen saf C++ özelliklerine odaklanmaktadır .

Yavaş derleme süreleri

C/C++'daki kaynak dosyalar arasındaki doğal arabirim , başlık dosyalarıdır . Bir başlık dosyası her değiştirildiğinde, başlık dosyasını içeren tüm kaynak dosyaların kodlarını yeniden derlemesi gerekir. Başlık dosyaları, önişlemcinin bir sonucu olarak metinsel ve içeriğe bağlı oldukları için yavaştır. C, başlık dosyalarında yalnızca sınırlı miktarda bilgiye sahiptir, en önemlileri yapı bildirimleri ve işlev prototipleridir. C++, sınıflarını başlık dosyalarında saklar ve yalnızca genel değişkenlerini ve genel işlevlerini (yapıları ve işlev prototipleriyle C gibi) değil, aynı zamanda özel işlevlerini de ortaya çıkarır. Bu, bu özel işlevleri her değiştirdiğinizde, başlık dosyasını içeren tüm kaynak dosyaların gereksiz yeniden derlenmesini zorlar. Bu sorun, sınıfların şablon olarak yazıldığı yerde büyür , tüm kodlarını yavaş başlık dosyalarına girmeye zorlar, tüm C++ standart kitaplığında olduğu gibi . Bu nedenle büyük C++ projelerinin derlenmesi nispeten yavaş olabilir. Sorun büyük ölçüde modern derleyicilerde önceden derlenmiş başlıklar veya C++20'ye eklenen modül sistemi kullanılarak çözülür ; gelecekteki C++ standartları, modülleri kullanarak standart kitaplığın işlevselliğini ortaya çıkarmayı planlıyor.

<iostream>'in genel biçim durumu

C++ <iostream>, C'den farklı olarak <stdio.h>küresel bir biçim durumuna dayanır. Bu , bir fonksiyonun bir hatadan sonra ancak global format durumunu sıfırlamadan önce kontrol akışını kesmesi gerektiğinde istisnalarla birlikte çok kötü uyum sağlar . Bunun için bir düzeltme , Boost kitaplıklarında ve C++ Standard Library'nin bir parçasında uygulanan Resource Acquisition Is Initialization (RAII) kullanmaktır .

<iostream>kitaplık kullanılmasa bile, dahil edildiğinde gereksiz ek yüke neden olan statik oluşturucular kullanır. Kötü performansın başka bir kaynağı da çıktı yaparken std::endlyerine yanlış kullanılmasıdır, \naynı zamanda .flush(). C++ <iostream>, varsayılan olarak senkronizedir ve <stdio.h>komut satırı yoğun uygulamalarda performans sorunlarına neden olabilir. Kapatmak performansı artırabilir ancak bazı sipariş garantilerinden vazgeçmeye zorlar.

Burada, bir istisnanın işlevi kesintiye uğrattığı bir örnek, daha önce std::coutonaltılıktan ondalık sayıya geri yüklenebilir. Catch deyimindeki hata numarası, muhtemelen birinin istediği gibi olmayan onaltılık olarak yazılacaktır:

#include <iostream>
#include <vector>

int main() {
  try {
    std::cout << std::hex
              << 0xFFFFFFFF << '\n';
    // std::bad_alloc will be thrown here:
    std::vector<int> vector(0xFFFFFFFFFFFFFFFFull);
    std::cout << std::dec; // Never reached
                           // (using scopes guards would have fixed that issue 
                           //  and made the code more expressive)
  } 
  catch (const std::exception& e) {
    std::cout << "Error number: " << 10 << '\n';  // Not in decimal
  }
}

<iostream>Sonunda değiştirilmesi gereken eskiyen bir arayüz olan C++ standartları kuruluşunun bazı üyeleri tarafından bile kabul edilmektedir . Bu tasarım, kitaplık uygulayıcılarını performansı büyük ölçüde etkileyen çözümleri benimsemeye zorlar.

C++20 std::format, küresel biçimlendirme durumunu ortadan kaldıran ve iostreams'deki diğer sorunları ele alan eklendi. Örneğin, catch cümlesi artık şu şekilde yazılabilir:

std::cout << std::format("Error number: {}\n", 10);

hangi akış durumundan etkilenmez. Çalışma zamanında yapılan gerçek biçimlendirme nedeniyle ek yük getirebilir.

yineleyiciler

C++ Standart Kitaplığında gömülü olan Standart Şablon Kitaplığı'nın (STL) felsefesi, yineleyiciler kullanarak şablonlar biçiminde genel algoritmalar kullanmaktır . Modern derleyiciler bu tür küçük soyutlamaları iyi bir şekilde optimize etse de, ilk derleyiciler yineleyiciler gibi küçük nesneleri kötü bir şekilde optimize ettiler , Alexander Stepanov bunu "soyutlama cezası" olarak nitelendirdi. Öğe aralıklarını belirtmek için yineleyici çiftlerini kullanan arayüz de eleştirildi. C++20 standart kitaplığının aralıkları tanıtması bu sorunu çözmelidir.

Büyük bir sorun, yineleyicilerin genellikle C++ kapsayıcılarında yığın ayrılmış verilerle ilgilenmesi ve veriler kaplar tarafından bağımsız olarak taşınırsa geçersiz hale gelmesidir. Kapsayıcının boyutunu değiştiren işlevler, genellikle kendisine işaret eden tüm yineleyicileri geçersiz kılar ve tehlikeli tanımsız davranış durumları yaratır . Burada, std::stringkabın öbek üzerindeki boyutunu değiştirmesi nedeniyle for döngüsündeki yineleyicilerin geçersiz kılındığı bir örnek verilmiştir :

#include <iostream>
#include <string>

int main() {
  std::string text = "One\nTwo\nThree\nFour\n";
  // Let's add an '!' where we find newlines
  for (auto it = text.begin(); it != text.end(); ++it) {
    if (*it == '\n') {
      // it =
      text.insert(it, '!') + 1;
      // Without updating the iterator this program has
      // undefined behavior and will likely crash
    }
  }
  std::cout << text;
}

Tek tip başlatma sözdizimi

C ++ 11 düzgün başlatma sözdizimi ve std :: initializer_list sınıfların iç işleyişini bağlı olarak farklı şekilde tetiklenir aynı sözdizimini paylaşır. Bir std::initializer_list yapıcısı varsa, buna çağrılır. Aksi takdirde, normal kurucular, tek tip başlatma sözdizimi ile çağrılır. Bu hem yeni başlayanlar hem de uzmanlar için kafa karıştırıcı olabilir

#include <iostream>
#include <vector>

int main() {
  int integer1{10};                 // int
  int integer2(10);                 // int
  std::vector<int> vector1{10, 0};  // std::initializer_list
  std::vector<int> vector2(10, 0);  // std::size_t, int

  std::cout << "Will print 10\n" << integer1 << '\n';
  std::cout << "Will print 10\n" << integer2 << '\n';

  std::cout << "Will print 10,0,\n";

  for (const auto& item : vector1) {
    std::cout << item << ',';
  }

  std::cout << "\nWill print 0,0,0,0,0,0,0,0,0,0,\n";

  for (const auto& item : vector2) {
    std::cout << item << ',';
  }
}

istisnalar

Sıfır genel gider ilkesinin istisnalarla uyumlu olmadığına dair endişeler var. Çoğu modern uygulama, istisnalar etkinleştirildiğinde ancak kullanılmadığında sıfır performans ek yüküne sahiptir, ancak özel durum işleme sırasında ve tabloları açma ihtiyacı nedeniyle ikili boyutta bir ek yüke sahiptir. Birçok derleyici, ikili ek yükü kurtarmak için dilden istisnaların devre dışı bırakılmasını destekler. İstisnalar, devlet idaresi için güvenli olmadığı için de eleştirildi. Bu güvenlik sorunu, C++ istisnalarını güvenli hale getirmenin ötesinde yararlı olduğu kanıtlanmış RAII deyiminin icadına yol açmıştır.

Kaynak kodunda dize değişmezlerinin kodlanması

C++ dize değişmezleri, C'ninkiler gibi, içlerindeki metnin karakter kodlamasını dikkate almazlar: bunlar yalnızca bir bayt dizisidir ve C++ stringsınıfı aynı ilkeyi izler. Kaynak kodu (C++ 11'den beri) bir hazır bilgi için bir kodlama talep edebilmesine rağmen, derleyici kaynak değişmezin seçilen kodlamasının içine konulan baytlar için "doğru" olduğunu doğrulamaya çalışmaz ve çalışma zamanının karakter kodlamasını uygula. Java, Python veya C# gibi karakter kodlamalarını zorlamaya çalışan diğer dillere alışkın olan programcılar bunu genellikle dilin bir kusuru olarak görürler.

Aşağıdaki örnek program olayı göstermektedir.

#include <iostream>
#include <string>
// note that this code is no longer valid in C++20
int main() {
  // all strings are declared with the UTF-8 prefix

  // file encoding determines the encoding of å and Ö
  std::string auto_enc = u8"Vår gård på Öland!";
  // this text is well-formed in both ISO-8859-1 and UTF-8
  std::string ascii = u8"Var gard pa Oland!";
  // explicitly use the ISO-8859-1 byte-values for å and Ö
  // this is invalid UTF-8
  std::string iso8859_1 = u8"V\xE5r g\xE5rd p\xE5 \xD6land!";
  // explicitly use the UTF-8 byte sequences for å and Ö
  // this will display incorrectly in ISO-8859-1
  std::string utf8 = u8"V\xC3\xA5r g\xC3\xA5rd p\xC3\xA5 \xC3\x96land!";

  std::cout << "byte-count of automatically-chosen, [" << auto_enc
            << "] = " << auto_enc.length() << '\n';
  std::cout << "byte-count of ASCII-only [" << ascii << "] = " << ascii.length()
            << '\n';
  std::cout << "byte-count of explicit ISO-8859-1 bytes [" << iso8859_1
            << "] = " << iso8859_1.length() << '\n';
  std::cout << "byte-count of explicit UTF-8 bytes [" << utf8
            << "] = " << utf8.length() << '\n';
}

"Unicode UTF-8 dize değişmezi" anlamına gelen C++11 'u8' önekinin varlığına rağmen, bu programın çıktısı aslında kaynak dosyanın metin kodlamasına (veya derleyicinin ayarlarına) bağlıdır - çoğu derleyiciye dönüştürme yapması söylenebilir. kaynak dosyaları derlemeden önce belirli bir kodlamaya dönüştürün). Kaynak dosya UTF-8 kullanılarak kodlandığında ve çıktı, girdisini UTF-8 olarak ele alacak şekilde yapılandırılmış bir terminalde çalıştırıldığında, aşağıdaki çıktı elde edilir:

byte-count of automatically-chosen, [Vår gård på Öland!] = 22
byte-count of ASCII-only [Var gard pa Oland!] = 18
byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18
byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

Çıkış terminali, ISO-8859 örnek dizesindeki geçersiz UTF-8 baytlarını görüntüden çıkardı. Programın çıktısını bir Hex döküm yardımcı programından geçirmek, program çıktısında hala mevcut olduklarını ve bunları kaldıran terminal uygulaması olduğunu ortaya çıkaracaktır.

Ancak, aynı kaynak dosya bunun yerine ISO-8859-1'e kaydedilip yeniden derlendiğinde, programın aynı terminaldeki çıktısı şöyle olur:

byte-count of automatically-chosen, [Vr grd p land!] = 18
byte-count of ASCII-only [Var gard pa Oland!] = 18
byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18
byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

Önerilen bir çözüm, kaynak kodlamayı tüm derleyiciler arasında güvenilir hale getirmektir.

Kod şişmesi

Bazı eski C++ uygulamaları kod şişmesi oluşturmakla suçlanmıştır .

Ayrıca bakınız

Referanslar

Atıfta bulunulan eserler

daha fazla okuma

  • Ian Joyner (1999). Kapsüllenmemiş Nesneler: Java, Eiffel ve C++?? (Nesne ve Bileşen Teknolojisi) . Prentice Salonu PTR; 1. baskı. ISBN'si 978-0130142696.
  • Peter Seibel (2009). İş Başında Kodlayıcılar: Programlama Zanaatına İlişkin Düşünceler . Apress. ISBN'si 978-1430219484.

Dış bağlantılar