Mengapa kita menempatkan fungsi anggota pribadi di header?

16

Jawaban mengapa kita meletakkan variabel anggota pribadi dalam header C ++ adalah bahwa ukuran kelas harus diketahui pada titik-titik di mana instance dideklarasikan sehingga kompiler dapat menghasilkan kode yang bergerak dengan tepat tentang stack.

Mengapa kita perlu menempatkan anggota pribadi di header?

Tetapi apakah ada alasan untuk mendeklarasikan fungsi pribadi dalam definisi kelas?

Alternatif dasarnya adalah idiom jerawat tetapi tanpa tipuan berlebihan.

Apakah fitur bahasa ini lebih dari sekadar kesalahan historis?

Praxeolitic
sumber

Jawaban:

11

Fungsi anggota privat mungkin virtual, dan dalam implementasi umum C ++ (yang menggunakan vtable) urutan khusus dan jumlah fungsi virtual diperlukan untuk diketahui oleh semua klien kelas. Ini berlaku bahkan jika satu atau lebih fungsi anggota virtual adalah private.

Tampaknya ini seperti "meletakkan kereta di depan kuda", karena pilihan implementasi kompiler seharusnya tidak mempengaruhi spesifikasi bahasa. Namun, pada kenyataannya bahasa C ++ sendiri dikembangkan bersamaan dengan implementasi yang berjalan ( Cfront ), yang menggunakan vtables.

Greg Hewgill
sumber
4
"pilihan implementasi seharusnya tidak mempengaruhi bahasa" hampir merupakan kebalikan dari mantra C ++. Spesifikasi ini tidak mengamanatkan implementasi tertentu, tetapi ada banyak aturan yang dibuat khusus untuk memenuhi persyaratan pilihan implementasi yang sangat efisien.
Ben Voigt
Ini terdengar sangat masuk akal. Apakah ada sumber tentang ini?
Praxeolitic
@Praxeolitic: Anda dapat membaca tentang tabel metode Virtual .
Greg Hewgill
1
@Praxeolitic - temukan salinan lama 'The Refernce Manual C ++ Beranotasi' ( stroustrup.com/arm.html ) - penulis berbicara tentang berbagai detail implementasi dan bagaimana ia membentuk bahasa.
Michael Kohne
Saya tidak yakin ini benar-benar menjawab pertanyaan. Bagi saya itu hanya membahas satu sisi tertentu - meskipun dengan baik - dan meninggalkan sisanya: Mengapa kita perlu menempatkan fungsi anggota pribadi non-virtual di header? Tentu, murni untuk konsistensi / kesederhanaan adalah satu argumen - tetapi idealnya kita juga memiliki alasan mekanistik dan / atau filosofis. Jadi, saya telah menambahkan jawaban yang saya percaya menjelaskan ini sebagai pilihan desain yang sangat disengaja dengan efek yang sangat menguntungkan.
underscore_d
13

Jika Anda membiarkan metode ditambahkan ke kelas di luar definisi, mereka dapat ditambahkan di mana saja , di file apa saja, oleh siapa saja.

Itu akan segera memberikan akses sepele kode klien ke anggota data pribadi dan terlindungi.

Setelah Anda menyelesaikan definisi kelas, tidak ada cara untuk menandai beberapa file sebagai diberkati secara khusus oleh penulis untuk memperluasnya - hanya ada unit terjemahan datar. Jadi, satu-satunya cara yang masuk akal untuk memberitahu kompiler bahwa serangkaian metode tertentu adalah resmi, atau diberkati oleh penulis kelas, adalah dengan mendeklarasikannya di dalam kelas.


Perhatikan bahwa kami memiliki akses langsung ke memori dalam C ++, yang berarti umumnya sepele untuk membuat tipe bayangan dengan tata letak memori yang sama dengan kelas Anda, tambahkan metode saya sendiri (atau buat semua data publik), dan reinterpret_cast. Atau saya dapat menemukan kode fungsi pribadi Anda, atau membongkarnya. Atau cari alamat fungsi di tabel simbol dan panggil atau langsung.

Penentu akses ini tidak mencoba mencegah serangan ini, karena itu tidak mungkin. Mereka hanya menunjukkan bagaimana suatu kelas seharusnya digunakan.

Tak berguna
sumber
1
Definisi kelas bisa merujuk ke entitas wadah fungsi yang disembunyikan dari kode klien. Atau, ini bisa dibalik dan definisi kelas tradisional lengkap yang disembunyikan dari kode klien dapat menunjuk antarmuka yang terlihat yang hanya menggambarkan anggota publik dan dapat mengungkapkan ukurannya.
Praxeolitic
1
Tentu, tetapi model kompilasi tidak memberikan cara yang masuk akal untuk menghentikan kode klien yang menempatkan versinya sendiri dari wadah tersembunyi atau antarmuka yang terlihat berbeda dengan pengakses tambahan.
berguna
Itu poin yang bagus, tetapi bukankah ini benar untuk definisi semua fungsi anggota tidak di header?
Praxeolitic
1
Semua fungsi anggota dideklarasikan di dalam kelas. Intinya adalah bahwa Anda tidak dapat menambahkan fungsi anggota baru yang setidaknya tidak dideklarasikan dalam definisi kelas (dan aturan satu definisi mencegah banyak definisi).
berguna
Lalu bagaimana dengan satu wadah aturan fungsi pribadi?
Praxeolitic
8

Jawaban yang diterima menjelaskan hal ini untuk fungsi pribadi virtual , tetapi itu hanya menjawab satu sisi khusus dari pertanyaan, yang jauh lebih terbatas daripada apa yang ditanyakan OP. Jadi, kita perlu ulang: mengapa kita harus mendeklarasikan fungsi pribadi non-virtual di header?

Jawaban lain menunjukkan fakta bahwa kelas harus dideklarasikan dalam satu blok - setelah itu mereka disegel dan tidak dapat ditambahkan. Itulah yang akan Anda lakukan dengan menghilangkan untuk mendeklarasikan metode pribadi di header lalu mencoba mendefinisikannya di tempat lain. Poin yang bagus. Mengapa beberapa pengguna kelas dapat memperbanyaknya dengan cara yang tidak bisa diamati oleh pengguna lain? Metode pribadi adalah bagian dari itu dan tidak dikecualikan dari ini. Tapi kemudian Anda bertanya mengapa mereka termasuk, dan tampaknya agak tautologis. Mengapa pengguna kelas harus tahu tentang mereka? Jika tidak terlihat, pengguna tidak dapat menambahkan, dan hei presto.

Jadi, saya ingin memberikan jawaban bahwa, alih-alih hanya memasukkan metode pribadi secara default, memberikan poin tertentu agar dapat dilihat oleh pengguna. Alasan mekanistik untuk fungsi pribadi non-virtual yang membutuhkan deklarasi publik diberikan dalam GotW # 100 Herb Sutter tentang idiom Pimpl sebagai bagian dari alasannya. Saya tidak akan melanjutkan tentang Pimpl di sini, karena saya yakin kita semua tahu tentang itu. Tapi inilah bagian yang relevan:

Dalam C ++, ketika apa pun dalam perubahan definisi kelas file header, semua pengguna kelas itu harus dikompilasi ulang - bahkan jika satu-satunya perubahan adalah anggota kelas privat yang bahkan pengguna kelas tidak dapat mengakses. Ini karena model build C ++ didasarkan pada inklusi tekstual, dan karena C ++ mengasumsikan bahwa penelepon mengetahui dua hal utama tentang kelas yang dapat dipengaruhi oleh anggota pribadi:

  • Ukuran dan Tata Letak : [anggota dan fungsi virtual - cukup jelas dan bagus untuk kinerja, tetapi tidak mengapa kami ada di sini]
  • Fungsi : Kode panggilan harus dapat menyelesaikan panggilan ke fungsi anggota kelas, termasuk fungsi pribadi yang tidak dapat diakses yang kelebihan fungsi nonprivat - jika fungsi pribadi lebih cocok, kode panggilan akan gagal dikompilasi. (C ++ mengambil keputusan desain yang disengaja untuk melakukan resolusi kelebihan sebelum pemeriksaan aksesibilitas untuk alasan keamanan. Misalnya, dirasakan bahwa mengubah aksesibilitas fungsi dari pribadi ke publik tidak boleh mengubah makna kode panggilan hukum.)

Sutter tentu saja merupakan sumber yang sangat andal sebagai anggota Komite, jadi dia tahu "keputusan desain yang disengaja" ketika dia melihatnya. Dan gagasan untuk mewajibkan deklarasi publik tentang metode pribadi sebagai cara untuk menghindari perubahan semantik atau aksesibilitas yang tidak disengaja nantinya mungkin merupakan alasan yang paling meyakinkan. Syukurlah, karena semuanya tampak tidak berguna sebelumnya sekarang!

underscore_d
sumber
" Tapi kemudian kamu bertanya mengapa, dan jawaban itu tampaknya tautologis. Lagi, mengapa pengguna kelas perlu tahu tentang mereka? " Tidak, itu tidak tautologis. Pengguna tidak perlu "perlu tahu tentang mereka". Yang perlu terjadi adalah Anda harus dapat mencegah orang lain memperpanjang kelas tanpa persetujuan penulis kelas. Jadi semua anggota tersebut harus dinyatakan di muka. Bahwa ini menyebabkan pengguna tahu tentang anggota pribadi hanyalah konsekuensi yang diperlukan.
Nicol Bolas
1
@NicolBolas Pasti. Itu mungkin kata-kata yang tidak terlalu bagus di pihak saya. Maksud saya, jawabannya hanya menjelaskan visibilitas metode pribadi sebagai konsekuensi dari aturan (sangat valid) yang mencakup banyak hal, daripada memberikan alasan tentang visibilitas metode pribadi secara khusus. Tentu saja, jawaban sebenarnya adalah kombinasi dari Useless, gnasher, dan milikku. Saya hanya ingin memberikan perspektif lain yang belum ada di sini.
underscore_d
4

Ada dua alasan untuk melakukan ini.

Pertama, sadari bahwa specifier akses adalah untuk kompiler , dan tidak relevan saat runtime. Mengakses anggota pribadi di luar ruang lingkup adalah kesalahan kompilasi .

Keringkasan yg padat isinya

Pertimbangkan fungsi yang pendek, satu atau dua baris. Itu ada untuk mengurangi replikasi kode di tempat lain, yang juga memiliki keuntungan karena dapat mengubah cara suatu algoritma atau apa pun yang bekerja di satu tempat, bukan banyak (misalnya mengubah algoritma penyortiran).

Apakah Anda lebih suka memiliki satu atau dua baris cepat di header, atau memiliki prototipe fungsi di sana plus implementasi di suatu tempat? Lebih mudah ditemukan di header, dan untuk fungsi pendek, jauh lebih tepat untuk memiliki implementasi terpisah.

Ada keuntungan besar lainnya, yaitu ...

Fungsi sebaris

Fungsi pribadi mungkin dapat digarisbawahi, dan ini mengharuskannya berada di header. Pertimbangkan ini:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

Fungsi pribadi mungkin dapat digarisbawahi bersama dengan fungsi publik. Itu dilakukan atas kebijakan kompiler, karena inlinekata kunci secara teknis merupakan saran , bukan keharusan.

Komunitas
sumber
Semua fungsi yang didefinisikan di dalam tubuh kelas ditandai secara otomatis inline, tidak ada alasan untuk menggunakan kata kunci di sana.
Ben Voigt
@ BenVoigt begitu. Saya tidak menyadarinya, dan saya telah menggunakan C ++ paruh waktu cukup lama. Bahasa itu tidak pernah berhenti mengejutkan saya dengan nugget kecil seperti itu.
Saya tidak berpikir metode harus di header untuk diuraikan. Itu hanya perlu berada di unit kompilasi yang sama. Apakah ada kasus di mana masuk akal untuk memiliki unit kompilasi terpisah untuk kelas?
Samuel Danielson
@SamuelDanielson benar, tetapi fungsi pribadi harus dalam definisi kelas. Gagasan "pribadi" menyiratkan itu adalah bagian dari kelas. Mungkin saja memiliki fungsi nonmember dalam .cppfile yang diuraikan oleh fungsi anggota yang didefinisikan di luar definisi kelas, tetapi fungsi seperti itu tidak akan bersifat pribadi.
Isi dari pertanyaan itu adalah "adakah alasan untuk mendeklarasikan fungsi pribadi dalam definisi kelas [sic, dimana konteks menunjukkan OP benar-benar berarti deklarasi kelas]". Anda sedang berbicara tentang mendefinisikan fungsi pribadi. Ini tidak menjawab pertanyaan. @SamuelDanielson Saat ini KPP berarti fungsi dapat berada di mana saja dalam suatu proyek dan mempertahankan peluang yang sama untuk digarisbawahi. Sedangkan untuk membagi kelas menjadi beberapa unit terjemahan, kasus paling sederhana adalah kelasnya hanya besar dan Anda ingin membaginya secara semantis menjadi beberapa file sumber. Saya yakin kelas-kelas besar seperti itu tidak disarankan, tetapi bagaimanapun
underscore_d
2

Alasan lain untuk memiliki metode pribadi dalam file header: Ada kasus di mana metode inline publik tidak lebih dari memanggil satu atau beberapa metode pribadi. Memiliki metode pribadi di header berarti bahwa panggilan ke metode publik dapat sepenuhnya diuraikan ke kode sebenarnya dari metode pribadi, dan inlining tidak berhenti dengan panggilan ke metode pribadi. Bahkan dari unit kompilasi yang berbeda (dan metode publik biasanya dipanggil dari unit kompilasi yang berbeda).

Tentu saja ada alasannya juga bahwa kompiler tidak dapat mendeteksi masalah dengan resolusi kelebihan jika tidak mengetahui semua metode, termasuk yang pribadi.

gnasher729
sumber
Poin bagus tentang inlining! Saya membayangkan ini sangat relevan untuk stdlib dan perpustakaan lain dengan metode yang didefinisikan inline. (jika tidak begitu banyak untuk kode internal, di mana KPP dapat melakukan hampir semua hal melintasi batas TU)
underscore_d
0

Ini untuk memungkinkan fungsi-fungsi itu untuk mengakses anggota pribadi. Kalau tidak, Anda akan membutuhkannya frienddi tajuk.

Jika ada fungsi yang bisa mengakses anggota pribadi kelas maka pribadi tidak akan berguna.

ratchet freak
sumber
1
Mungkin saya seharusnya lebih eksplisit dalam pertanyaan - maksud saya mengapa bahasa ini dirancang seperti ini? Mengapa harus atau mengapa desain ini bagus?
Praxeolitic