Chapter sebelumnya memperkenalkan struct sebagai wadah data terkait, tetapi kita belum membahas isi wadah itu secara serius. Member variables — sering disebut data members — adalah variabel-variabel yang dideklarasikan di dalam definisi struct, dan setiap objek struct menyimpan salinannya sendiri. Pada Chapter ini kita mengatur ulang struct dengan member yang lebih beragam, menanyakan ukuran memori struct lewat sizeof, dan mengintip mengapa dua struct berisi tipe yang sama bisa berbeda ukuran. Di akhir, kita menerima kabar baik: struct juga boleh berisi fungsi, yang pembahasannya menunggu di Section Pemrograman Berorientasi Objek.

Aturan Member Variables #

Member ditulis di dalam badan struct dengan format deklarasi variabel biasa: tipe diikuti nama, dan setiap deklarasi diakhiri titik koma. Kita boleh memberi nilai awal langsung di dalam definisi struct (int stok = 0;) — fitur yang disebut default member initializer — sehingga setiap objek baru mulai dari nilai yang masuk akal. Setiap objek struct memegang set lengkap member-nya sendiri: mengubah a.stok tidak pernah memengaruhi b.stok.

cpp
#include <iostream>
#include <string>
#include <vector>

// Member boleh bertipe apa pun yang sudah kita kenal, termasuk struct lain
struct Waktu {
    int jam;
    int menit;
};

struct Acara {
    std::string nama;
    Waktu mulai;          // member bertipe struct lain
    std::vector<int> peserta;   // member bertipe container
    bool online;
};

// Dua struct untuk membedah padding
struct UrutLain {
    char kode;
    int jumlah;
    char tipe;
};

struct UrutSama {
    int jumlah;
    char kode;
    char tipe;
};

int main() {
    std::cout << "sizeof(char)    = " << sizeof(char) << " byte\n";
    std::cout << "sizeof(int)     = " << sizeof(int) << " byte\n";
    std::cout << "sizeof(double)  = " << sizeof(double) << " byte\n";
    std::cout << "sizeof(UrutLain)= " << sizeof(UrutLain) << " byte\n";
    std::cout << "sizeof(UrutSama)= " << sizeof(UrutSama) << " byte\n";

    Acara webinar;
    webinar.nama = "Belajar C++ Modern";
    webinar.mulai.jam = 19;
    webinar.mulai.menit = 30;
    webinar.peserta = {101, 205, 88};
    webinar.online = true;

    std::cout << webinar.nama << " mulai pukul "
              << webinar.mulai.jam << "." << webinar.mulai.menit << "\n";
    std::cout << "Terdaftar: " << webinar.peserta.size() << " peserta\n";

    return 0;
}

Output yang Dihasilkan:

text
sizeof(char)    = 1 byte
sizeof(int)     = 4 byte
sizeof(double)  = 8 byte
sizeof(UrutLain)= 12 byte
sizeof(UrutSama)= 8 byte
Belajar C++ Modern mulai pukul 19.30
Terdaftar: 3 peserta

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 6-9 (struct Waktu): struct kecil dengan dua member int. Tipe struct yang sudah didefinisikan boleh dipakai sebagai tipe member di struct lain, selama definisinya muncul lebih dulu di file.
  2. Baris 11-16 (struct Acara): struct Acara menggabungkan lima jenis member sekaligus — std::string, struct Waktu, std::vector<int>, dan bool. Aksesnya bertingkat: webinar.mulai.jam membaca member jam dari member mulai.
  3. Baris 19-29: dua struct dengan member yang sama persis, hanya urutannya berbeda. Inilah bahan eksperimen ukuran memori di bawah.
  4. Baris 32-36 (sizeof(...)): operator sizeof yang kita pelajari pada Chapter sizeof dan limits kini diterapkan pada tipe struct, bukan hanya tipe fundamental.
  5. Baris 38-43: tiap member webinar diisi satu per satu; webinar.peserta = {101, 205, 88} mengisi vector member lewat initializer list, persis seperti vector biasa.

Tipe Apa Saja yang Boleh Menjadi Member #

Aturannya longgar: member boleh bertipe apa pun yang ukurannya sudah diketahui compiler saat struct didefinisikan. Kita sudah membuktikan sebagian di contoh di atas; daftar lengkapnya memetakan apa yang sudah kita kuasai sejauh ini:

  • Tipe fundamental: int, double, char, bool, dan kawan-kawannya.
  • Tipe dari standard library: std::string, std::vector<T>, std::array<T, N>.
  • Struct lain, termasuk struct yang didefinisikan di dalam struct (dibahas khusus pada Chapter Nested Struct).
  • Array C-style: double nilai[3]; boleh menjadi member dengan ukuran tetap.
  • Pointer dan reference: Produk* next; atau int& alias; — member reference memiliki aturan inisialisasi khusus karena tidak bisa dibiarkan kosong; kita sentuh kembali saat membahas class.

Satu larangan yang perlu diingat: struct tidak boleh memiliki member bertipe dirinya sendiri (struct Node { Node bawah; };), karena compiler tidak bisa menentukan ukurannya — struct yang memuat dirinya akan tak berhingga ukurannya. Sebaliknya, pointer ke dirinya sendiri diperbolehkan (Node* bawah;) karena ukuran pointer sudah pasti; pola inilah fondasi linked list pada Section Struktur Data dan Algoritma.

Ukuran struct dan Padding #

Output di atas memunculkan pertanyaan: UrutLain dan UrutSama berisi satu int dan dua char yang identik, tetapi ukurannya 12 lawan 8 byte. Penjelasannya adalah padding — byte pengisi yang diselipkan compiler agar setiap member berada di alamat yang kelipatan ukuran alignment-nya. CPU membaca int paling efisien bila alamatnya kelipatan 4, sehingga pada UrutLain compiler menyisipkan 3 byte kosong setelah kode (offset 1-3), lalu jumlah duduk di offset 4. Setelah tipe di offset 8, struct ditambahkan 3 byte penutup supaya ukuran totalnya kelipatan 4 (alignment terbesar di struct tersebut). Pada UrutSama, jumlah langsung di offset 0 dan kedua char menempel di offset 4-5, cukup 2 byte penutup — total 8 byte.

text
UrutLain (12 byte): [kode][x][x][x][jumlah int][tipe][x][x][x]
UrutSama (8 byte):  [jumlah int][kode][tipe][x][x]

Praktik terbaiknya sederhana: pada struct yang datanya banyak, urutkan member besar-ke-kecil; pada struct kecil, biarkan urutan yang paling masuk akal secara logika. Pada bab-bab performa nanti, kita juga akan berkenalan dengan atribut alignas yang memberi kendali alignment secara eksplisit.

Struct Juga Boleh Berisi Fungsi #

Satu pratinjau sebelum kita lanjut: badan struct boleh juga berisi member functions, sehingga perilaku dan datanya tinggal bersebelahan.

cpp
struct Timer {
    int detik = 0;
    int dalamMenit() {        // member function — pratinjau
        return detik / 60;
    }
};

Kode di atas benar dan bisa dikompilasi: Timer t{90}; t.dalamMenit() menghasilkan 1. Mengapa fitur ini berguna dan bagaimana menulisnya dengan benar akan kita bahas tuntas mulai dari Chapter Konsep Class dan Object; untuk sisa Section ini, struct kita perlakukan sebagai wadah data murni.

Referensi resmi tentang data members tersedia pada halaman data members dan tentang operator ukuran pada sizeof di cppreference. Berikutnya kita akan mengisi struct sejak awal keberadaannya: inisialisasi dengan daftar nilai dan designated initializers C++20.