Operator stream

Operator << dan >> sebagai friend untuk mencetak tipe buatan sendiri.

Daftar isi

Topik ini membahas operator << dan >> yang membuat tipe buatan sendiri dapat dicetak ke layar dan dibaca dari masukan. Pembahasan mencakup alasan kedua operator harus ditulis sebagai free function, peran deklarasi friend, serta kebiasaan yang menjaga objek tetap utuh apabila pembacaan gagal. Contohnya berupa kelas Titik yang dicetak dan dibaca melalui stream, yaitu saluran data masukan dan keluaran milik pustaka standar.

Mengapa operator « bukan member function#

Compiler memandang ekspresi std::cout << t sebagai pemanggilan operator dengan std::cout sebagai operand kiri dan objek t sebagai operand kanan. Member function selalu menjadikan operand kiri sebagai objek kelasnya, sedangkan operand kiri di sini adalah stream, bukan Titik. Kelas std::ostream milik pustaka standar tidak dapat disunting untuk menambah member function baru. Satu-satunya bentuk yang memungkinkan adalah free function dengan dua parameter, yaitu operator<<(std::ostream&, const Titik&). Pola yang sama berlaku terbalik untuk >> pada std::istream, dengan parameter objek yang tidak konstan.

Dengan mengikuti konvensi tersebut, tipe buatan sendiri terasa menyatu dengan tipe standar seperti int dan std::string dalam satu ekspresi pencetakan. Tidak ada sintaks khusus yang perlu dipelajari pembaca kode, karena std::cout << titik terbaca persis seperti std::cout << 42.

Ada kalanya logika pencetakan lebih nyaman ditulis sebagai member function, misalnya karena memerlukan akses ke banyak member private sekaligus. Pola yang lazim adalah member function publik seperti cetak(std::ostream&) yang menuliskan isinya, lalu free function operator<< yang cukup meneruskan pekerjaannya. Bentuknya seperti berikut.

cpp Idiom: member function yang meneruskan pekerjaan pencetakan
// tidak-dijalankan
std::ostream& operator<<(std::ostream& keluaran, const Titik& t) {
    return t.cetak(keluaran);   // cetak() adalah member function publik
}

Dengan idiom ini, logika pencetakan tetap berada di dalam kelas, sedangkan antarmuka yang dapat dirantai tetap berupa free function. Program kecil seperti Titik pada topik ini cukup memakai friend secara langsung, sedangkan kelas yang pencetakannya panjang biasanya memisahkan keduanya.

Deklarasi friend dan akses member private#

Implementasi operator<< perlu membaca member private x dan y. Dua jalan tersedia, yaitu memakai accessor publik atau mendeklarasikan fungsi itu sebagai friend di dalam kelas. Deklarasi friend memberi fungsi luar akses penuh ke member private, dan lazim ditulis tepat di samping member datanya sehingga ketergantungannya terlihat. Kedua operator stream hampir selalu ditulis sebagai friend karena keduanya memang bagian dari antarmuka kelas, meskipun definisinya berada di luar kelas.

Deklarasi friend di dalam kelas bukan deklarasi member, sehingga definisinya ditulis di luar kelas sebagaimana free function biasa. Nama fungsi friend juga tidak dicari di antara member kelas, melainkan ditemukan berpedoman pada argumen pemanggilannya, sehingga pemanggilan std::cout << t tetap menemukan fungsinya. Definisi yang singkat boleh ditulis langsung di dalam badan kelas, dan fungsinya tetap berstatus free function yang mendapat akses friend.

Program berikut mendefinisikan kelas Titik beserta kedua operator streamnya. Jalankan programnya dan perhatikan pencetakan yang dirantai serta pembacaan dari sebuah string tetap.

cpp titik-stream.cpp
#include <iostream>
#include <sstream>

class Titik {
public:
    Titik(int x, int y) : x(x), y(y) {}

private:
    int x;
    int y;

    // kedua operator mengakses member private, sehingga dideklarasikan friend
    friend std::ostream& operator<<(std::ostream& keluaran, const Titik& t);
    friend std::istream& operator>>(std::istream& masukan, Titik& t);
};

// operator << mencetak objek, lalu mengembalikan stream yang sama agar dapat dirantai
std::ostream& operator<<(std::ostream& keluaran, const Titik& t) {
    keluaran << "(" << t.x << ", " << t.y << ")";
    return keluaran;
}

// operator >> membaca data dan memperbarui objek hanya jika pembacaan berhasil
std::istream& operator>>(std::istream& masukan, Titik& t) {
    int x = 0;
    int y = 0;
    if (masukan >> x >> y) {
        t.x = x;
        t.y = y;
    }
    return masukan;
}

int main() {
    Titik a(3, 4);
    Titik b(0, 0);

    std::cout << "a = " << a << " dan b = " << b << "\n";

    // masukan diambil dari string agar keluaran program selalu sama
    std::istringstream sumber("7 -2");
    if (sumber >> b) {
        std::cout << "b = " << b << " setelah dibaca\n";
    }
    return 0;
}
text Keluaran
a = (3, 4) dan b = (0, 0)
b = (7, -2) setelah dibaca

Baris std::cout << "a = " << a << " dan b = " << b memuat empat pemanggilan operator<< yang dirantai. Rantai itu bekerja karena fungsi mengembalikan stream yang sama, yaitu parameter keluaran, sehingga hasil satu pemanggilan menjadi operand kiri pemanggilan berikutnya. Tipe baliknya adalah std::ostream&, bukan nilai, karena stream tidak dapat disalin.

Membaca masukan dengan operator »#

Operator >> menerima stream masukan dan referensi yang tidak konstan (Titik&) karena isinya akan diperbarui. Kebiasaan yang aman adalah membaca ke variabel lokal terlebih dahulu, lalu menyalinnya ke member hanya apabila seluruh pembacaan berhasil. Apabila member langsung ditimpa, objek bisa berada dalam keadaan setengah terbaca ketika stream kehabisan data di tengah jalan.

Keberhasilan pembacaan diperiksa melalui stream itu sendiri. Ekspresi if (masukan >> x >> y) menguji keadaan stream, yang bernilai benar selama tidak ada kegagalan. Apabila gagal, misalnya karena teksnya bukan bilangan, stream memasuki keadaan gagal (fail state) dan setiap pembacaan berikutnya ditolak sampai keadaannya dipulihkan.

Sama seperti pencetakan, pembacaan pun dapat dirantai karena operator>> mengembalikan streamnya kembali. Ekspresi std::cin >> a >> b dibaca compiler sebagai (std::cin >> a) >> b, sehingga dua objek terisi oleh satu baris. Rantai berhenti segera setelah stream gagal, dan keadaan gagal itu dapat diuji setelah seluruh rantai selesai.

Kesalahan umum#

  • Mengembalikan void dari operator<<. Rantai seperti std::cout << a << b memerlukan stream sebagai tipe balik, sehingga kode gagal dikompilasi. Kembalikan referensi ke stream yang sama.
  • Mengembalikan stream berdasarkan nilai. Stream tidak dapat disalin, sehingga deklarasi dengan tipe balik std::ostream tanpa referensi ditolak compiler. Tipe balik yang benar adalah referensi.
  • Melupakan const pada parameter objek untuk operator<<. Objek yang bersifat konstan tidak dapat dikirim ke parameter tanpa const, sehingga pencetakannya gagal dikompilasi. Parameter objek untuk pencetakan selalu const.
  • Menimpa member langsung tanpa memeriksa keberhasilan pembacaan pada operator>>. Saat stream gagal di tengah data, objek berakhir setengah terbaca. Baca ke variabel lokal, lalu salin setelah seluruh pembacaan berhasil.

Ringkasan#

  • operator<< dan operator>> ditulis sebagai free function karena operand kirinya adalah stream milik pustaka standar yang tidak dapat disunting.
  • Keduanya mengembalikan referensi ke stream yang sama agar pemanggilannya dapat dirantai.
  • Deklarasi friend dipakai agar implementasi dapat mengakses member private tanpa membuka accessor.
  • operator>> memperbarui objeknya hanya setelah seluruh pembacaan berhasil.

Referensi#