Selamat datang kembali ke C++ - Modern C++

Sejak dibuat, C++ telah menjadi salah satu bahasa pemrograman yang paling banyak digunakan di dunia. Program C++ yang ditulis dengan baik cepat dan efisien. Bahasa ini lebih fleksibel daripada bahasa lain: Bahasa ini dapat bekerja pada tingkat abstraksi tertinggi, dan turun pada tingkat silikon. C++ memasok pustaka standar yang sangat dioptimalkan. Ini memungkinkan akses ke fitur perangkat keras tingkat rendah, untuk memaksimalkan kecepatan dan meminimalkan persyaratan memori. C++ dapat membuat hampir semua jenis program: Game, driver perangkat, HPC, cloud, desktop, tersemat, dan aplikasi seluler, dan banyak lagi. Bahkan pustaka dan kompilator untuk bahasa pemrograman lainnya ditulis dalam C++.

Salah satu persyaratan asli untuk C++ adalah kompatibilitas mundur dengan bahasa C. Akibatnya, C++ selalu mengizinkan pemrograman gaya C, dengan pointer mentah, array, string karakter null-terminated, dan fitur lainnya. Mereka mungkin memungkinkan performa yang hebat, tetapi juga dapat menelurkan bug dan kompleksitas. Evolusi C++ telah menekankan fitur yang sangat mengurangi kebutuhan untuk menggunakan idiom gaya C. Fasilitas pemrograman C lama masih ada ketika Anda membutuhkannya. Namun, dalam kode C++ modern Anda harus membutuhkannya lebih sedikit dan kurang. Kode C++ modern lebih sederhana, lebih aman, lebih elegan, dan masih secepat sebelumnya.

Bagian berikut memberikan gambaran umum tentang fitur utama C++modern. Kecuali disebutkan sebaliknya, fitur yang tercantum di sini tersedia di C++11 dan yang lebih baru. Di pengkompilasi Microsoft C++, Anda dapat mengatur /std opsi pengkompilasi untuk menentukan versi standar mana yang akan digunakan untuk proyek Anda.

Sumber daya dan penunjuk cerdas

Salah satu kelas utama bug dalam pemrograman gaya C adalah kebocoran memori. Kebocoran sering disebabkan oleh kegagalan untuk memanggil delete memori yang dialokasikan dengan new. C++ modern menekankan prinsip akuisisi sumber daya adalah inisialisasi (RAII). Idenya sederhana. Sumber daya (memori timbunan, handel file, soket, dan sebagainya) harus dimiliki oleh objek. Objek tersebut membuat, atau menerima, sumber daya yang baru dialokasikan di konstruktornya, dan menghapusnya di destruktornya. Prinsip RAII menjamin bahwa semua sumber daya dikembalikan dengan benar ke sistem operasi ketika objek pemilik keluar dari cakupan.

Untuk mendukung adopsi prinsip RAII yang mudah, Pustaka Standar C++ menyediakan tiga jenis penunjuk cerdas: std::unique_ptr, std::shared_ptr, dan std::weak_ptr. Pointer pintar menangani alokasi dan penghapusan memori yang dimilikinya. Contoh berikut menunjukkan kelas dengan anggota array yang dialokasikan pada tumpukan dalam panggilan ke make_unique(). Panggilan ke new dan delete dienkapsulasi oleh unique_ptr kelas . widget Ketika objek keluar dari cakupan, destruktor unique_ptr akan dipanggil dan akan melepaskan memori yang dialokasikan untuk array.

#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

Jika memungkinkan, gunakan penunjuk cerdas untuk mengelola memori timbunan. Jika Anda harus menggunakan new operator dan delete secara eksplisit, ikuti prinsip RAII. Untuk informasi selengkapnya, lihat Masa pakai objek dan manajemen sumber daya (RAII).

std::string dan std::string_view

String gaya C adalah sumber bug utama lainnya. Dengan menggunakan std::string dan std::wstring, Anda dapat menghilangkan hampir semua kesalahan yang terkait dengan string gaya C. Anda juga mendapatkan manfaat dari fungsi anggota untuk mencari, menambahkan, prepending, dan sebagainya. Keduanya sangat dioptimalkan untuk kecepatan. Saat meneruskan string ke fungsi yang hanya memerlukan akses baca-saja, di C++17 Anda dapat menggunakan std::string_view untuk manfaat performa yang lebih besar.

std::vector dan kontainer Pustaka Standar lainnya

Kontainer pustaka standar semuanya mengikuti prinsip RAII. Mereka menyediakan iterator untuk traversal elemen yang aman. Dan, mereka sangat dioptimalkan untuk performa dan telah diuji secara menyeluruh untuk kebenaran. Dengan menggunakan kontainer ini, Anda menghilangkan potensi bug atau inefisiensi yang mungkin diperkenalkan dalam struktur data kustom. Alih-alih array mentah, gunakan vector sebagai kontainer berurutan di C++.

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

Gunakan map (bukan unordered_map) sebagai kontainer asosiatif default. Gunakan set, multimap, dan multiset untuk degenerasi dan multi kasus.

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

Saat pengoptimalan performa diperlukan, pertimbangkan untuk menggunakan:

  • Kontainer asosiatif yang tidak diurutkan seperti unordered_map. Ini memiliki overhead per elemen yang lebih rendah dan pencarian waktu konstan, tetapi mungkin lebih sulit untuk digunakan dengan benar dan efisien.
  • Diurutkan vector. Untuk informasi selengkapnya, lihat Algoritma.

Jangan gunakan array gaya C. Untuk API lama yang memerlukan akses langsung ke data, gunakan metode pengaktif seperti f(vec.data(), vec.size()); sebagai gantinya. Untuk informasi selengkapnya tentang kontainer, lihat Kontainer Pustaka Standar C++.

Algoritma Pustaka Standar

Sebelum Anda berasumsi bahwa Anda perlu menulis algoritma kustom untuk program Anda, pertama-tama tinjau algoritma Pustaka Standar C++. Pustaka Standar berisi berbagai algoritma yang terus berkembang untuk banyak operasi umum seperti mencari, mengurutkan, memfilter, dan mengacak. Pustaka matematika sangat luas. Di C++17 dan yang lebih baru, versi paralel dari banyak algoritma disediakan.

Berikut adalah beberapa contoh penting:

  • for_each, algoritma traversal default (bersama dengan perulangan berbasis for rentang).
  • transform, untuk modifikasi elemen kontainer yang tidak ada di tempat
  • find_if, algoritma pencarian default.
  • sort, lower_bound, dan algoritma pengurutan dan pencarian default lainnya.

Untuk menulis komparator, gunakan lambda < bernama ketat dan gunakan ketika Anda bisa.

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 alih-alih nama jenis eksplisit

C++11 memperkenalkan auto kata kunci untuk digunakan dalam deklarasi variabel, fungsi, dan templat. auto memberi tahu pengkompilasi untuk menyimpulkan jenis objek sehingga Anda tidak perlu mengetiknya secara eksplisit. auto sangat berguna ketika jenis yang disimpulkan adalah templat berlapis:

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

Perulangan berbasis for rentang

Iterasi gaya C atas array dan kontainer rentan terhadap kesalahan pengindeksan dan juga melelahkan untuk mengetik. Untuk menghilangkan kesalahan ini, dan membuat kode Anda lebih mudah dibaca, gunakan perulangan berbasis for rentang dengan kontainer Pustaka Standar dan array mentah. Untuk informasi selengkapnya, lihat Pernyataan berbasis for rentang.

#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 ekspresi alih-alih makro

Makro di C dan C++ adalah token yang diproses oleh prapemroses sebelum kompilasi. Setiap instans token makro diganti dengan nilai atau ekspresi yang ditentukan sebelum file dikompilasi. Makro umumnya digunakan dalam pemrograman gaya C untuk menentukan nilai konstanta waktu kompilasi. Namun, makro rentan terhadap kesalahan dan sulit di-debug. Dalam C++modern, Anda harus lebih memilih constexpr variabel untuk konstanta waktu kompilasi:

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

Inisialisasi seragam

Di C++modern, Anda dapat menggunakan inisialisasi kurung kurawal untuk jenis apa pun. Bentuk inisialisasi ini sangat nyaman ketika menginisialisasi array, vektor, atau kontainer lainnya. Dalam contoh berikut, v2 diinisialisasi dengan tiga instans S. v3 diinisialisasi dengan tiga instans S yang diinisialisasi sendiri menggunakan kurung kurawal. Pengkompilasi menyimpulkan jenis setiap elemen berdasarkan jenis yang dideklarasikan dari 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} };

}

Untuk informasi selengkapnya, lihat Inisialisasi kurung kurawal.

Memindahkan semantik

C++ modern menyediakan semantik pemindahan, yang memungkinkan untuk menghilangkan salinan memori yang tidak perlu. Dalam versi bahasa yang lebih lama, salinan tidak dapat ditolak dalam situasi tertentu. Operasi pemindahan mentransfer kepemilikan sumber daya dari satu objek ke objek berikutnya tanpa membuat salinan. Beberapa kelas memiliki sumber daya seperti memori timbunan, handel file, dan sebagainya. Saat menerapkan kelas pemilik sumber daya, Anda dapat menentukan konstruktor pemindahan dan memindahkan operator penugasan untuk kelas tersebut. Pengkompilasi memilih anggota khusus ini selama resolusi kelebihan beban dalam situasi di mana salinan tidak diperlukan. Jenis kontainer Pustaka Standar memanggil konstruktor pemindahan pada objek jika ditentukan. Untuk informasi selengkapnya, lihat Memindahkan Konstruktor dan Memindahkan Operator Penugasan (C++).

Ekspresi Lambda

Dalam pemrograman gaya C, fungsi dapat diteruskan ke fungsi lain dengan menggunakan penunjuk fungsi. Penunjuk fungsi tidak nyaman untuk dipertahankan dan dipahami. Fungsi yang mereka rujuk dapat didefinisikan di tempat lain dalam kode sumber, jauh dari titik di mana ia dipanggil. Juga, mereka tidak tipe-aman. C++ modern menyediakan objek fungsi, kelas yang mengambil alih operator() operator, yang memungkinkan mereka dipanggil seperti fungsi. Cara paling nyaman untuk membuat objek fungsi adalah dengan ekspresi lambda sebaris. Contoh berikut menunjukkan cara menggunakan ekspresi lambda untuk meneruskan objek fungsi, yang find_if akan dipanggil fungsi pada setiap elemen di vektor:

    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; });

Ekspresi [=](int i) { return i > x && i < y; } lambda dapat dibaca sebagai "fungsi yang mengambil satu argumen jenis int dan mengembalikan boolean yang menunjukkan apakah argumen lebih besar dari x dan kurang dari y." Perhatikan bahwa variabel x dan y dari konteks di sekitarnya dapat digunakan dalam lambda. [=] menentukan bahwa variabel tersebut diambil berdasarkan nilai; dengan kata lain, ekspresi lambda memiliki salinan nilai tersebut sendiri.

Pengecualian

C++ modern menekankan pengecualian, bukan kode kesalahan, sebagai cara terbaik untuk melaporkan dan menangani kondisi kesalahan. Untuk informasi selengkapnya, lihat Praktik terbaik C++ modern untuk pengecualian dan penanganan kesalahan.

std::atomic

Gunakan struktur Pustaka std::atomic Standar C++ dan jenis terkait untuk mekanisme komunikasi antar alur.

std::variant (C++17)

Serikat pekerja umumnya digunakan dalam pemrograman gaya C untuk menghemat memori dengan memungkinkan anggota dari berbagai jenis untuk menempati lokasi memori yang sama. Namun, serikat tidak aman jenis dan rentan terhadap kesalahan pemrograman. C++17 memperkenalkan std::variant kelas sebagai alternatif yang lebih kuat dan aman untuk serikat. Fungsi ini std::visit dapat digunakan untuk mengakses anggota jenis dengan variant cara yang aman.

Baca juga

Referensi Bahasa C++
Ekspresi Lambda
Pustaka Standar C++
Kesesuaian bahasa Microsoft C/C++