Apa yang dimaksud deklarasi maju di C ++?

215

Di: http://www.learncpp.com/cpp-tutorial/19-header-files/

Berikut ini disebutkan:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;
}

Kami menggunakan deklarasi maju sehingga kompiler akan tahu apa " add" saat mengkompilasi main.cpp. Seperti yang disebutkan sebelumnya, menulis deklarasi maju untuk setiap fungsi yang ingin Anda gunakan yang hidup di file lain bisa membosankan dengan cepat.

Bisakah Anda menjelaskan lebih lanjut " deklarasi maju "? Apa masalahnya jika kita menggunakannya dalam main()fungsinya?

Kesederhanaan
sumber
1
"Deklarasi maju" sebenarnya hanyalah deklarasi. Lihat (akhir) jawaban ini: stackoverflow.com/questions/1410563/…
sbi

Jawaban:

381

Mengapa forward-mendeklarasikan diperlukan dalam C ++

Kompiler ingin memastikan Anda tidak membuat kesalahan pengejaan atau memberikan jumlah argumen yang salah ke fungsi. Jadi, ini menegaskan bahwa ia pertama kali melihat deklarasi 'add' (atau jenis, kelas, atau fungsi lainnya) sebelum digunakan.

Ini benar-benar hanya memungkinkan kompiler untuk melakukan pekerjaan yang lebih baik dalam memvalidasi kode, dan memungkinkannya untuk merapikan ujung yang longgar sehingga dapat menghasilkan file objek yang tampak rapi. Jika Anda tidak perlu meneruskan mendeklarasikan sesuatu, kompiler akan menghasilkan file objek yang harus berisi informasi tentang semua dugaan yang mungkin tentang apa fungsi 'add' mungkin. Dan linker harus mengandung logika yang sangat pintar untuk mencoba dan mencari tahu 'add' yang ingin Anda panggil, ketika fungsi 'add' dapat hidup dalam file objek yang berbeda, linker bergabung dengan yang menggunakan add untuk menghasilkan dll atau exe. Mungkin saja linker mendapatkan tambahan yang salah. Katakanlah Anda ingin menggunakan int add (int a, float b), tetapi tidak sengaja lupa untuk menuliskannya, tetapi linker menemukan int add yang sudah ada (int a, int b) dan berpikir itu yang benar dan menggunakannya. Kode Anda akan dikompilasi, tetapi tidak akan melakukan apa yang Anda harapkan.

Jadi, hanya untuk membuat hal-hal eksplisit dan menghindari tebakan dll, kompiler bersikeras Anda menyatakan semuanya sebelum digunakan.

Perbedaan antara deklarasi dan definisi

Selain itu, penting untuk mengetahui perbedaan antara deklarasi dan definisi. Deklarasi hanya memberikan kode yang cukup untuk menunjukkan seperti apa sesuatu itu, jadi untuk suatu fungsi, ini adalah tipe pengembalian, konvensi pemanggilan, nama metode, argumen dan tipenya. Tetapi kode untuk metode ini tidak diperlukan. Untuk sebuah definisi, Anda memerlukan deklarasi dan juga kode untuk fungsinya juga.

Bagaimana deklarasi maju dapat secara signifikan mengurangi waktu pembuatan

Anda bisa memasukkan deklarasi fungsi ke file .cpp atau .h Anda saat ini dengan # menyertakan header yang sudah berisi deklarasi fungsi. Namun, ini dapat memperlambat kompilasi Anda, terutama jika Anda # memasukkan header ke dalam .h alih-alih .cpp dari program Anda, karena segala sesuatu yang # termasuk .h yang sedang Anda tulis akan berakhir # termasuk semua header Anda menulis #termasuk juga. Tiba-tiba, kompiler memiliki #include halaman dan halaman kode yang perlu dikompilasi bahkan ketika Anda hanya ingin menggunakan satu atau dua fungsi. Untuk menghindari ini, Anda dapat menggunakan deklarasi maju dan cukup ketik sendiri deklarasi fungsi di bagian atas file. Jika Anda hanya menggunakan beberapa fungsi, ini benar-benar dapat membuat kompilasi Anda lebih cepat dibandingkan dengan selalu menyertakan header. Untuk proyek yang sangat besar,

Pecahkan referensi siklik di mana dua definisi saling menggunakan

Selain itu, deklarasi maju dapat membantu Anda memutus siklus. Di sinilah dua fungsi keduanya mencoba menggunakan satu sama lain. Ketika ini terjadi (dan ini adalah hal yang benar-benar sah untuk dilakukan), Anda dapat #mengikutsertakan satu file header, tetapi file header itu mencoba untuk #mengikutsertakan file header yang sedang Anda tulis .... yang kemudian #termasuk header lainnya , yang termasuk # yang Anda tulis. Anda terjebak dalam situasi ayam dan telur dengan masing-masing file header mencoba memasukkan # lainnya. Untuk mengatasi ini, Anda dapat meneruskan-mendeklarasikan bagian-bagian yang Anda butuhkan di salah satu file dan meninggalkan #sertakan dari file itu.

Misalnya:

File Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

File Wheel.h

Hmm ... deklarasi Mobil diperlukan di sini karena Wheel memiliki pointer ke Mobil, tetapi Car.h tidak dapat dimasukkan di sini karena akan menghasilkan kesalahan kompiler. Jika Car.h dimasukkan, maka akan mencoba untuk memasukkan Wheel.h yang akan mencakup Car.h yang akan mencakup Wheel.h dan ini akan berlangsung selamanya, jadi sebagai gantinya kompiler menimbulkan kesalahan. Solusinya adalah dengan meneruskan mendeklarasikan Mobil:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Jika Roda kelas memiliki metode yang perlu memanggil metode mobil, metode tersebut dapat didefinisikan dalam Wheel.cpp dan Wheel.cpp sekarang dapat memasukkan Car.h tanpa menyebabkan siklus.

Scott Langham
sumber
4
deklarasi maju juga diperlukan ketika suatu fungsi ramah untuk dua atau banyak kelas
Barun
1
Hei Scott, pada titik Anda tentang waktu membangun: Apakah Anda mengatakan itu adalah praktik umum / terbaik untuk selalu meneruskan mendeklarasikan dan memasukkan header yang diperlukan dalam file .cpp? Dari membaca jawaban Anda tampaknya memang begitu, tapi saya bertanya-tanya apakah ada peringatan?
Zepee
5
@ Zepee Ini keseimbangan. Untuk membangun cepat, saya akan mengatakan itu adalah latihan yang baik dan saya sarankan mencobanya. Namun, perlu upaya dan beberapa baris kode tambahan yang mungkin perlu dipertahankan dan diperbarui jika nama jenis dll masih sedang diubah (meskipun alat semakin baik dalam mengubah nama barang secara otomatis). Jadi ada tradeoff. Saya telah melihat basis kode di mana tidak ada yang mengganggu. Jika Anda mendapati diri Anda mengulangi definisi maju yang sama, Anda selalu bisa memasukkannya ke dalam file header terpisah dan memasukkannya, seperti: stackoverflow.com/questions/4300696/what-is-the-iosfwd-header
Scott Langham
deklarasi maju diperlukan ketika file header merujuk satu sama lain: yaitu stackoverflow.com/questions/396084/…
Nicholas Hamilton
1
Saya bisa melihat ini memungkinkan para devs lain di tim saya menjadi warga negara yang benar-benar buruk basis kode. Jika Anda tidak memerlukan komentar dengan pernyataan maju, seperti // From Car.hAnda dapat membuat beberapa situasi berbulu mencoba menemukan definisi di ujung jalan, dijamin.
Dagrooms
25

Kompilator mencari setiap simbol yang digunakan dalam unit terjemahan saat ini dinyatakan sebelumnya atau tidak dalam unit saat ini. Ini hanya masalah gaya menyediakan semua tanda tangan metode di awal file sumber sementara definisi diberikan nanti. Penggunaan signifikan dari itu adalah ketika Anda menggunakan pointer ke kelas sebagai variabel anggota kelas lain.

//foo.h
class bar;    // This is useful
class foo
{
    bar* obj; // Pointer or even a reference.
};

// foo.cpp
#include "bar.h"
#include "foo.h"

Jadi, gunakan deklarasi maju di kelas kapan pun memungkinkan. Jika program Anda hanya memiliki fungsi (dengan file tajuk ho), maka memberikan prototipe di awal hanya masalah gaya. Bagaimanapun, ini terjadi jika file header ada dalam program normal dengan header yang hanya memiliki fungsi.

Mahesh
sumber
12

Karena C ++ diuraikan dari atas ke bawah, kompiler perlu tahu tentang hal-hal sebelum mereka digunakan. Jadi, ketika Anda referensi:

int add( int x, int y )

pada fungsi utama, kompiler perlu mengetahuinya. Untuk membuktikan ini coba pindahkan ke bawah fungsi utama dan Anda akan mendapatkan kesalahan kompiler.

Jadi ' Deklarasi Teruskan ' adalah apa yang tertulis di kaleng. Ini menyatakan sesuatu sebelum penggunaannya.

Secara umum Anda akan menyertakan penerusan deklarasi dalam file header dan kemudian memasukkan file header dengan cara yang sama seperti iostream disertakan.

Nick
sumber
12

Istilah " pernyataan maju " di C ++ sebagian besar hanya digunakan untuk deklarasi kelas . Lihat (akhir) jawaban ini untuk alasan mengapa "deklarasi maju" suatu kelas benar-benar hanyalah deklarasi kelas sederhana dengan nama mewah.

Dengan kata lain, "maju" hanya menambah pemberat istilah, karena setiap deklarasi dapat dilihat sebagai maju sejauh itu menyatakan beberapa pengidentifikasi sebelum digunakan.

(Mengenai apakah deklarasi yang bertentangan dengan definisi , lihat lagi Apa perbedaan antara definisi dan deklarasi? )

sbi
sumber
2

Ketika kompilator melihatnya add(3, 4)perlu tahu apa artinya itu. Dengan deklarasi forward Anda pada dasarnya memberitahu kompiler itu addadalah fungsi yang mengambil dua int dan mengembalikan sebuah int. Ini adalah informasi penting untuk kompiler karena ia perlu menempatkan 4 dan 5 di representasi yang benar ke stack dan perlu tahu apa jenis barang yang dikembalikan oleh add.

Pada saat itu, compiler tidak khawatir tentang yang sebenarnya pelaksanaan add, yaitu di mana itu (atau jika ada yang bahkan satu) dan jika mengkompilasi. Itu muncul kemudian, setelah mengkompilasi file sumber ketika linker dipanggil.

René Nyffenegger
sumber
1
int add(int x, int y); // forward declaration using function prototype

Bisakah Anda menjelaskan lebih lanjut "deklarasi maju"? Apa masalahnya jika kita menggunakannya di fungsi main ()?

Itu sama dengan #include"add.h". Jika Anda tahu, preprocessor memperluas file yang Anda sebutkan #include, dalam file .cpp di mana Anda menulis #includearahan. Itu artinya, jika Anda menulis#include"add.h" , Anda mendapatkan hal yang sama, seolah-olah Anda melakukan "pernyataan maju".

Saya berasumsi bahwa add.hmemiliki baris ini:

int add(int x, int y); 
Nawaz
sumber
1

satu tambahan cepat tentang: biasanya Anda menempatkan referensi ke dalam file header milik file .c (pp) di mana fungsi / variabel dll diimplementasikan. dalam contoh Anda akan terlihat seperti ini: add.h:

extern int add (int a, int b);

kata kunci extern menyatakan bahwa fungsi tersebut sebenarnya dideklarasikan dalam file eksternal (bisa juga berupa perpustakaan, dll.). main.c Anda akan terlihat seperti ini:

#termasuk 
#termasuk "add.h"

int main ()
{
.
.
.

mendongkrak
sumber
Tapi, bukankah kita hanya menempatkan deklarasi di file header? Saya pikir ini adalah mengapa fungsi didefinisikan dalam "add.cpp", dan dengan demikian menggunakan deklarasi maju? Terima kasih.
Kesederhanaan
0

Satu masalah adalah, bahwa kompiler tidak tahu, jenis nilai apa yang dikirimkan oleh fungsi Anda; Diasumsikan, bahwa fungsi mengembalikan sebuah intdalam kasus ini, tetapi ini bisa benar karena dapat salah. Masalah lain adalah, bahwa kompiler tidak tahu, argumen apa yang diharapkan fungsi Anda, dan tidak bisa memperingatkan Anda, jika Anda memberikan nilai yang salah. Ada aturan "promosi" khusus, yang berlaku ketika lewat, katakan nilai floating point ke fungsi yang tidak dideklarasikan (kompiler harus melebarkannya untuk mengetik ganda), yang seringkali tidak, apa fungsi sebenarnya harapkan, yang menyebabkan sulit untuk menemukan bug saat run-time.

Beladau
sumber