Mengapa metode struct tidak harus dideklarasikan dalam C++?

Ambil contoh kode berikut:

#include <iostream>
#include <string>

int main()
{
    print("Hello!");
}

void print(std::string s) {
    std::cout << s << std::endl;
}

Saat mencoba membangun ini, saya mendapatkan yang berikut:

program.cpp: In function ‘int main()’:
program.cpp:6:16: error: ‘print’ was not declared in this scope

Itu masuk akal.

Jadi mengapa saya bisa melakukan konsep serupa dalam sebuah struct, tetapi tidak dimarahi karenanya?

struct Snake {
    ...

    Snake() {
        ...
        addBlock(Block(...));
    }

    void addBlock(Block block) {
        ...
    }

    void update() {
        ...
    }

} snake1;

Saya tidak hanya tidak mendapatkan peringatan, tetapi program tersebut benar-benar dapat dikompilasi! Tanpa error! Apakah ini hanya sifat dari struct? Apa yang sedang terjadi disini? Jelas addBlock(Block) dipanggil sebelum metode ini dideklarasikan.


person Community    schedule 30.07.2014    source sumber
comment
ini sebenarnya pertanyaan yang bagus dan ada orang yang bertanya-tanya mengapa ini terjadi. terutama beberapa jawaban yang datang dalam bentuk ini adalah alasan historis, karena kompilasi dua fase mahal pada tahun 1976. tetapi C++ adalah semacam bajingan dengan beberapa bagian lama dan beberapa bagian baru, sehingga Anda mendapatkan perbedaan ini.   -  person v.oddou    schedule 30.07.2014
comment
Jawaban singkatnya: badan fungsi tidak dikompilasi sampai definisi kelas telah dikompilasi   -  person M.M    schedule 30.07.2014
comment
Secara praktis, hal ini dapat dilakukan karena kompiler harus melakukan backtrack hanya pada satu kelas, bukan keseluruhan file. Yang terakhir ini harganya terlalu mahal jika menggunakan teknologi era 90-an.   -  person MSalters    schedule 30.07.2014
comment
Ada pertanyaan serupa baru-baru ini: stackoverflow.com/questions/24925831/   -  person Tony Delroy    schedule 30.07.2014
comment
Mirip dengan kelas juga: ideone.com/Xw2dbF   -  person user3791372    schedule 30.07.2014
comment
Di D&E, Stroustrup mengatakan fitur ini telah diperkenalkan untuk menangani masalah pencarian nama. Misalnya, int i; struct s { void foo() { i = 42; } int i; }; Ketika foo didefinisikan di luar kelas, i jelas mengacu pada this->i, dan harus sama ketika definisi dipindahkan ke dalam kelas.   -  person dyp    schedule 30.07.2014


Jawaban (3)


struct dalam C++ sebenarnya adalah definisi class yang isinya adalah public, kecuali ditentukan lain dengan menyertakan bagian protected: atau private:.

Ketika kompilator melihat class atau struct, kompiler terlebih dahulu mencerna semua deklarasi dalam blok ({}) sebelum mengoperasikannya.

Dalam kasus metode reguler, kompiler belum melihat tipe yang dideklarasikan.

person NirMH    schedule 30.07.2014
comment
@juanchopanza: Ini sepenuhnya berlaku untuk tipe batin. Ketika kompilator melihat suatu Kelas atau Struktur, kompilator akan mencerna semua deklarasi terlebih dahulu, yang berarti deklarasi harus dapat dicerna. Jika deklarasi menggunakan tipe dalam, tipe dalam harus dideklarasikan (mungkin ditentukan tergantung pada deklarasinya). - person Mooing Duck; 30.07.2014
comment
@juanchopanza: Saya rasa kami mengalami kesalahan komunikasi. Saya mengatakan bahwa langkah pertama mencerna semua deklarasi menyiratkan bahwa deklarasi tersebut harus mengikuti aturan normal untuk tipe/tanda tangan yang dideklarasikan secara berurutan. Ini berulang dengan sempurna di tipe dalam. Setelah itu, definisi diurai. Saya tidak melihat ini sebagai pengecualian untuk tipe dalam, saya melihat ini sebagai tipe dalam yang mengikuti aturan yang sama persis dengan tipe luar. - person Mooing Duck; 30.07.2014
comment
@MooingDuck Anda benar, saya belum membaca komentar pertama Anda dengan cukup cermat. Saya rasa yang Anda maksud adalah ini: ideone.com/K40DDE, yang sangat masuk akal. - person juanchopanza; 30.07.2014

Standar C++ 3.4.1:

.4:

Nama yang digunakan dalam lingkup global, di luar fungsi, kelas, atau namespace yang dideklarasikan pengguna, harus dideklarasikan sebelum digunakan dalam lingkup global.

Inilah sebabnya mengapa variabel dan fungsi global tidak dapat digunakan sebelum deklarasi sebelumnya.

.5:

Nama yang digunakan dalam namespace yang dideklarasikan pengguna di luar definisi fungsi atau kelas apa pun harus dideklarasikan sebelum digunakan dalam namespace tersebut atau sebelum digunakan dalam namespace yang menyertakan namespacenya.

hal yang sama baru saja ditulis lagi karena paragraf .4 secara jelas membatasi perkataannya pada "global", paragraf ini sekarang mengatakan "omong-omong, itu juga benar dalam spesifikasi nama kawan..."

.7:

Nama yang digunakan dalam definisi kelas X di luar badan fungsi anggota atau definisi kelas bersarang29 harus dideklarasikan dengan salah satu cara berikut: — sebelum digunakan di kelas X atau menjadi anggota kelas dasar X (10.2) , atau — jika X adalah kelas bertumpuk dari kelas Y (9.7), sebelum definisi X di Y, atau harus menjadi anggota kelas dasar Y (pencarian ini berlaku untuk kelas terlampir Y, dimulai dengan kelas penutup paling dalam),30 atau — jika X adalah kelas lokal (9.8) atau merupakan kelas bersarang dari kelas lokal, sebelum definisi kelas X dalam blok yang menyertakan definisi kelas X, atau — jika X adalah kelas anggota namespace N, atau merupakan kelas bersarang dari kelas yang merupakan anggota N, atau merupakan kelas lokal atau kelas bersarang dalam kelas lokal dari suatu fungsi yang merupakan anggota N, sebelum definisi kelas X di namespace N atau di salah satu namespace terlampir N.

Saya pikir ini berbicara tentang semua kode yang tidak termasuk dalam kode yang dieksekusi cpu (misalnya kode deklaratif).

dan akhirnya bagian yang menarik:

3.3.7 Ruang lingkup kelas [basic.scope.class]

1 Aturan berikut menjelaskan ruang lingkup nama yang dideklarasikan di kelas.

1) Cakupan potensial dari nama yang dideklarasikan dalam suatu kelas tidak hanya terdiri dari wilayah deklaratif yang mengikuti titik deklarasi nama tersebut, tetapi juga semua badan fungsi, inisialisasi kurung kurawal atau sama dengan anggota data non-statis, dan argumen default di kelas itu (termasuk hal-hal seperti itu di kelas bersarang).

2) Nama N yang digunakan dalam kelas S harus mengacu pada deklarasi yang sama dalam konteksnya dan ketika dievaluasi ulang dalam lingkup S yang telah diselesaikan. Tidak diperlukan diagnostik untuk pelanggaran aturan ini.

3) Jika menyusun ulang deklarasi anggota di suatu kelas menghasilkan program alternatif yang valid berdasarkan (1) dan (2), maka program tersebut salah format, sehingga tidak diperlukan diagnostik.

khususnya, pada poin terakhir mereka menggunakan cara negatif untuk mendefinisikan bahwa "pemesanan apa pun dimungkinkan" karena jika pemesanan ulang akan mengubah pencarian maka ada masalah. ini adalah cara negatif untuk mengatakan "Anda dapat menyusun ulang apa pun dan tidak apa-apa, itu tidak mengubah apa pun".

secara efektif mengatakan, di kelas, deklarasi dicari dalam mode kompilasi dua fase.

person v.oddou    schedule 30.07.2014

"mengapa saya dapat menjalankan konsep serupa dalam sebuah struct, tetapi tidak dimarahi karenanya?"

Dalam definisi struct atau class Anda menyajikan antarmuka publik ke kelas dan akan lebih mudah untuk memahami, menelusuri, dan memelihara/memperbarui API tersebut jika disajikan dalam:

  • urutan yang dapat diprediksi, dengan
  • kekacauan minimal.

Untuk urutan yang dapat diprediksi, orang memiliki gayanya sendiri dan ada sedikit "seni" yang terlibat, tetapi misalnya saya menggunakan setiap penentu akses paling banyak satu kali dan selalu public sebelum protected sebelum private, lalu di dalamnya saya biasanya memasukkan typedefs, const data, konstruktor , destruktor, fungsi mutasi/non-konstan, fungsi const, statics, friends....

Untuk meminimalkan kekacauan, jika suatu fungsi didefinisikan di kelas, sebaiknya fungsi tersebut tidak dideklarasikan sebelumnya. Memiliki keduanya cenderung hanya mengaburkan antarmuka.

Ini berbeda dengan fungsi yang bukan anggota kelas - di mana orang yang menyukai pemrograman top-down menggunakan deklarasi fungsi dan menyembunyikan definisinya nanti di file - yaitu:

  • orang yang lebih menyukai gaya pemrograman bottom-up tidak akan senang jika dipaksa untuk memiliki deklarasi terpisah di kelas atau mengabaikan praktik pengelompokan berdasarkan penentu akses yang sering menimbulkan konflik

  • Kelas secara statistik lebih cenderung memiliki banyak fungsi yang sangat singkat, terutama karena mereka menyediakan enkapsulasi dan membungkus banyak akses anggota data yang sepele atau menyediakan operator yang kelebihan beban, operator casting, konstruktor implisit dan fitur kenyamanan lainnya yang tidak relevan dengan non-OO, fungsi non-anggota. Hal ini membuat pemisahan deklarasi dan definisi yang dipaksakan secara terus-menerus menjadi lebih menyakitkan bagi banyak kelas (tidak terlalu banyak di antarmuka publik di mana definisi mungkin berada dalam file terpisah, tetapi tentu saja untuk misalnya kelas-kelas dalam ruang nama anonim yang mendukung unit terjemahan saat ini).

  • Praktik terbaiknya adalah kelas tidak menjejalkan antarmuka yang sangat luas... Anda biasanya menginginkan inti fungsional dan kemudian beberapa fungsi kenyamanan opsional, setelah itu ada baiknya mempertimbangkan apa yang dapat ditambahkan sebagai fungsi non-anggota. std::string sering diklaim memiliki terlalu banyak fungsi anggota, meskipun menurut saya pribadi itu cukup masuk akal. Namun, ini juga berbeda dari file header yang mendeklarasikan antarmuka perpustakaan, di mana fungsionalitas yang lengkap dapat diharapkan untuk dijejali sehingga pemisahan implementasi inline menjadi lebih diinginkan.

person Tony Delroy    schedule 30.07.2014