Share via


C++ 'a yeniden hoş geldiniz - Modern C++

C++, kuruluşundan bu yana dünyanın en yaygın kullanılan programlama dillerinden biri haline gelmiştir. İyi yazılmış C++ programları hızlı ve verimlidir. Dil diğer dillerden daha esnektir: En yüksek soyutlama düzeylerinde ve silikon düzeyinde aşağıda çalışabilir. C++ yüksek oranda iyileştirilmiş standart kitaplıklar sağlar. Hızı en üst düzeye çıkarmak ve bellek gereksinimlerini en aza indirmek için alt düzey donanım özelliklerine erişim sağlar. C++ neredeyse her tür program oluşturabilir: Oyunlar, cihaz sürücüleri, HPC, bulut, masaüstü, eklenmiş ve mobil uygulamalar ve çok daha fazlası. Diğer programlama dilleri için kitaplıklar ve derleyiciler bile C++ dilinde yazılır.

C++ için özgün gereksinimlerden biri C diliyle geriye dönük uyumluluktu. Sonuç olarak, C++ her zaman ham işaretçiler, diziler, null ile sonlandırılan karakter dizeleri ve diğer özelliklerle C stili programlamaya izin verir. Bunlar harika bir performans sağlayabilir, ancak hataları ve karmaşıklığı da oluşturabilir. C++'ın evrimi, C stili deyimleri kullanma ihtiyacını büyük ölçüde azaltan özellikleri vurgulamıştır. Eski C programlama tesisleri ihtiyacınız olduğunda hala oradadır. Ancak, modern C++ kodunda bunlara daha az ve daha az ihtiyacınız olmalıdır. Modern C++ kodu daha basit, daha güvenli, daha zarif ve her zamanki gibi hızlıdır.

Aşağıdaki bölümlerde modern C++'ın ana özelliklerine genel bir bakış sağlanmaktadır. Aksi belirtilmediği sürece, burada listelenen özellikler C++11 ve sonraki sürümlerde kullanılabilir. Microsoft C++ derleyicisinde, projeniz için hangi standart sürümünün kullanılacağını belirtmek için derleyici seçeneğini ayarlayabilirsiniz /std .

Kaynaklar ve akıllı işaretçiler

C stili programlamadaki başlıca hata sınıflarından biri bellek sızıntısıdır. Sızıntılar genellikle ile ayrılan belleğin çağrılmamasından deletenewkaynaklanmıştır. Modern C++, kaynak edinme ilkesinin başlatma (RAII) olduğunu vurgular. Fikir basit. Kaynaklar (yığın belleği, dosya tanıtıcıları, yuvalar vb.) bir nesneye ait olmalıdır. Bu nesne, oluşturucusunda yeni ayrılan kaynağı oluşturur veya alır ve yok edicisinde siler. RAII ilkesi, sahip olan nesne kapsam dışına çıktığında tüm kaynakların düzgün bir şekilde işletim sistemine döndürüldüğünü garanti eder.

RAII ilkelerinin kolay benimsenmesini desteklemek için C++ Standart Kitaplığı üç akıllı işaretçi türü sağlar: std::unique_ptr, std::shared_ptrve std::weak_ptr. Akıllı işaretçi, sahip olduğu belleğin ayrılmasını ve silinmesini işler. Aşağıdaki örnekte, çağrısındaki make_unique()yığında ayrılmış bir dizi üyesine sahip bir sınıf gösterilmektedir. ve delete çağrıları new sınıfı tarafından unique_ptr kapsüllenir. Bir widget nesne kapsam dışına çıktığında, unique_ptr yıkıcı çağrılır ve dizi için ayrılan belleği serbest bırakır.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

Mümkün olduğunda yığın belleğini yönetmek için akıllı bir işaretçi kullanın. ve delete işleçlerini açıkça kullanmanız new gerekiyorsa RAII ilkesini izleyin. Daha fazla bilgi için bkz . Nesne ömrü ve kaynak yönetimi (RAII).

std::string ve std::string_view

C stili dizeler, hataların bir diğer önemli kaynağıdır. ve std::wstringkullanarakstd::string, C stili dizelerle ilişkili neredeyse tüm hataları ortadan kaldırabilirsiniz. Ayrıca, arama, ekleme, ön ödeme vb. için üye işlevlerinin avantajından da yararlanabilirsiniz. Her ikisi de hız için yüksek oranda optimize edilmiştir. Yalnızca salt okunur erişim gerektiren bir işleve dize geçirirken, C++17'de daha da yüksek performans avantajı için kullanabilirsiniz std::string_view .

std::vector ve diğer Standart Kitaplık kapsayıcıları

Standart kitaplık kapsayıcılarının tümü RAII ilkesini izler. Öğelerin güvenli geçişi için yineleyiciler sağlar. Ayrıca performans için son derece iyileştirilmiştir ve doğruluk açısından kapsamlı bir şekilde test edilmiştir. Bu kapsayıcıları kullanarak, özel veri yapılarında ortaya çıkabilecek hata veya verimsizlik olasılığını ortadan kaldırırsınız. Ham diziler yerine C++'ta sıralı kapsayıcı olarak kullanın vector .

vector<string> apples;
apples.push_back("Granny Smith");

Varsayılan ilişkilendirici kapsayıcı olarak (değilunordered_map) kullanın map . Degenerate ve multiset multi servis talepleri için , multimapve kullanınset.

map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";

Performans iyileştirmesi gerektiğinde şunları kullanmayı göz önünde bulundurun:

  • gibi unordered_mapsıralı olmayan ilişkilendirme kapsayıcıları. Bunlar daha düşük öğe başına ek yüke ve sabit zamanlı aramaya sahiptir, ancak bunların doğru ve verimli bir şekilde kullanılması daha zor olabilir.
  • Sıralandı vector. Daha fazla bilgi için bkz . Algoritmalar.

C stili diziler kullanmayın. Verilere doğrudan erişmesi gereken eski API'ler için bunun yerine gibi f(vec.data(), vec.size()); erişimci yöntemlerini kullanın. Kapsayıcılar hakkında daha fazla bilgi için bkz . C++ Standart Kitaplık Kapsayıcıları.

Standart Kitaplık algoritmaları

Programınız için özel bir algoritma yazmanız gerektiğini varsaymadan önce C++ Standart Kitaplık algoritmalarını gözden geçirin. Standart Kitaplık, arama, sıralama, filtreleme ve rastgele hale getirme gibi birçok yaygın işlem için sürekli büyüyen bir algoritma yelpazesi içerir. Matematik kitaplığı kapsamlıdır. C++17 ve sonraki sürümlerde birçok algoritmanın paralel sürümleri sağlanır.

Aşağıda bazı önemli örnekler verilmiştir:

  • for_each, varsayılan dolaşma algoritması (aralık tabanlı for döngülerle birlikte).
  • transform, kapsayıcı öğelerinin yerinde olmayan değişiklikleri için
  • find_if, varsayılan arama algoritmasıdır.
  • sort, lower_boundve diğer varsayılan sıralama ve arama algoritmaları.

Karşılaştırıcı yazmak için katı < kullanın ve uygun olduğunda adlandırılmış lambdalar kullanın.

auto comp = [](const widget& w1, const widget& w2)
     { return w1.weight() < w2.weight(); }

sort( v.begin(), v.end(), comp );

auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );

auto açık tür adları yerine

C++11 değişken, işlev ve şablon bildirimlerinde kullanmak üzere anahtar sözcüğünü kullanıma sunms auto . auto derleyiciye, açıkça yazmanız gerekmeyecek şekilde nesnenin türünü çıkarması için bildirir. auto özellikle çıkarılmış tür iç içe yerleştirilmiş bir şablon olduğunda kullanışlıdır:

map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++

Aralık tabanlı for döngüler

Diziler ve kapsayıcılar üzerinde C stili yineleme, dizin oluşturma hatalarına açıktır ve ayrıca yazmak için de yorucudur. Bu hataları ortadan kaldırmak ve kodunuzu daha okunabilir hale getirmek için hem Standart Kitaplık kapsayıcılarıyla hem de ham dizilerle aralık tabanlı for döngüleri kullanın. Daha fazla bilgi için bkz . Aralık tabanlı for deyim.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v {1,2,3};

    // C-style
    for(int i = 0; i < v.size(); ++i)
    {
        std::cout << v[i];
    }

    // Modern C++:
    for(auto& num : v)
    {
        std::cout << num;
    }
}

constexpr makrolar yerine ifadeler

C ve C++ içindeki makrolar, derlemeden önce önişlemci tarafından işlenen belirteçlerdir. Bir makro belirtecinin her örneği, dosya derilmeden önce tanımlı değeri veya ifadesiyle değiştirilir. Makrolar, derleme zamanı sabit değerlerini tanımlamak için C stili programlamada yaygın olarak kullanılır. Ancak makrolar hataya açıktır ve hata ayıklaması zordur. Modern C++ dilinde derleme zamanı sabitleri için değişkenleri tercih constexpr etmelisiniz:

#define SIZE 10 // C-style
constexpr int size = 10; // modern C++

Tekdüzen başlatma

Modern C++ dilinde, herhangi bir tür için ayraç başlatmayı kullanabilirsiniz. Bu başlatma biçimi özellikle diziler, vektörler veya diğer kapsayıcılar başlatılırken kullanışlıdır. Aşağıdaki örnekte, v2 üç örneğiyle Sbaşlatılır. v3 , küme ayraçları S kullanılarak başlatılan üç örneğiyle başlatılır. Derleyici, bildirilen türüne göre her öğenin türünü çıkarsar v3.

#include <vector>

struct S
{
    std::string name;
    float num;
    S(std::string s, float f) : name(s), num(f) {}
};

int main()
{
    // C-style initialization
    std::vector<S> v;
    S s1("Norah", 2.7);
    S s2("Frank", 3.5);
    S s3("Jeri", 85.9);

    v.push_back(s1);
    v.push_back(s2);
    v.push_back(s3);

    // Modern C++:
    std::vector<S> v2 {s1, s2, s3};

    // or...
    std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };

}

Daha fazla bilgi için bkz . Küme ayracı başlatma.

Semantiği taşıma

Modern C++, gereksiz bellek kopyalarını ortadan kaldırmayı mümkün kılan taşıma semantiği sağlar. Dilin önceki sürümlerinde, bazı durumlarda kopyalar kaçınılmazdı. Taşıma işlemi, kopyalama yapmadan bir kaynağın sahipliğini bir nesneden diğerine aktarır. Bazı sınıflar yığın belleği, dosya tanıtıcıları gibi kaynaklara sahip olur. Kaynağa sahip bir sınıf uyguladığınızda, bunun için bir taşıma oluşturucu ve taşıma atama işleci tanımlayabilirsiniz. Derleyici, bir kopyanın gerekli olmadığı durumlarda aşırı yükleme çözümlemesi sırasında bu özel üyeleri seçer. Standart Kitaplık kapsayıcı türleri, nesne tanımlanmışsa nesnelerde taşıma oluşturucuyu çağırır. Daha fazla bilgi için bkz . Oluşturucuları Taşıma ve Atama İşleçlerini Taşıma (C++).

Lambda ifadeleri

C stili programlamada bir işlev, işlev işaretçisi kullanılarak başka bir işleve geçirilebilir. İşlev işaretçilerinin bakımı ve anlaşılması zor. Başvuracakları işlev, kaynak kodun çağrıldığı noktadan çok uzak olan başka bir yerde tanımlanabilir. Ayrıca, bunlar tür açısından güvenli değildir. Modern C++, işlecini geçersiz kılan operator() ve işlev gibi çağrılmalarını sağlayan işlev nesneleri sağlar. İşlev nesneleri oluşturmanın en kullanışlı yolu satır içi lambda ifadeleridir. Aşağıdaki örnekte, bir işlev nesnesini geçirmek için lambda ifadesinin nasıl kullanılacağı ve işlevin find_if vektördeki her öğede nasıl çağrılacağı gösterilmektedir:

    std::vector<int> v {1,2,3,4,5};
    int x = 2;
    int y = 4;
    auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });

Lambda ifadesi [=](int i) { return i > x && i < y; } "türünde int tek bir bağımsız değişken alan ve bağımsız değişkenin değerinden büyük x ve küçük yolup olmadığını belirten bir boole döndüren işlev" olarak okunabilir. Lambda'da ve çevresindeki bağlamdan değişkenlerin xy kullanılabildiğini unutmayın. bu [=] değişkenlerin değere göre yakalandığını belirtir; başka bir deyişle lambda ifadesinin bu değerlerin kendi kopyaları vardır.

Özel durumlar

Modern C++ hata koşullarını bildirmenin ve işlemenin en iyi yolu olarak hata kodlarını değil özel durumları vurgular. Daha fazla bilgi için bkz . Özel durumlar ve hata işleme için modern C++ en iyi yöntemleri.

std::atomic

İş parçacıkları arası iletişim mekanizmaları için C++ Standart Kitaplık std::atomic yapısını ve ilgili türleri kullanın.

std::variant (C++17)

Birleşimler, farklı türlerdeki üyelerin aynı bellek konumunu kaplamasına olanak tanıyarak bellek tasarrufu sağlamak için C stili programlamada yaygın olarak kullanılır. Ancak birleşimler tür açısından güvenli değildir ve programlama hatalarına açıktır. C++17, sınıfı birleşimlere std::variant daha sağlam ve güvenli bir alternatif olarak tanıtır. işlevi, std::visit türün variant üyelerine tür güvenli bir şekilde erişmek için kullanılabilir.

Ayrıca bkz.

C++ Dil Başvurusu
Lambda İfadeleri
C++ Standart Kitaplığı
Microsoft C/C++ dil uyumluluğu