C ++ neden ayrı bir başlık dosyasına ihtiyaç duyuyor?


138

C ++ neden .cpp dosyasındaki ile aynı işlevlere sahip ayrı bir başlık dosyasına ihtiyaç duyduğunu hiç anlamadım. Sınıf oluşturmayı ve onları yeniden düzenlemeyi çok zorlaştırır ve projeye gereksiz dosyalar ekler. Ve sonra başlık dosyalarını dahil etmekle ilgili bir sorun var, ancak önceden eklenmiş olup olmadığını açıkça kontrol etmek zorunda.

C ++ 1998 yılında onaylandı, neden bu şekilde tasarlandı? Ayrı bir başlık dosyasına sahip olmanın ne gibi avantajları vardır?


Takip eden soru:

Tüm eklediğim .h dosyası olduğunda, derleyici içindeki kodu içeren .cpp dosyasını nasıl bulur? .Cpp dosyasının .h dosyasıyla aynı ada sahip olduğunu varsayar mı, yoksa aslında dizin ağacındaki tüm dosyaları arar mı?


2
Yalnızca tek bir dosyayı düzenlemek istiyorsanız lzz (www.lazycplusplus.com) adresine bakın.
Richard Corden

Yanıtlar:


105

Başlık dosyaları için başka kullanımlar olsa da, tanımları bildirimlerden ayırmayı soruyor gibisiniz.

Cevap, C ++ 'nın buna "ihtiyaç duymaması" dır. Her şeyi satır içinde işaretlerseniz (bir sınıf tanımında tanımlanan üye işlevleri için otomatiktir), ayırmaya gerek yoktur. Üstbilgi dosyalarındaki her şeyi tanımlayabilirsiniz.

Ayırmak isteyebileceğiniz nedenler :

  1. İnşa sürelerini iyileştirmek.
  2. Tanımlar için kaynak olmadan koda bağlanmak.
  3. Her şeyi "satır içi" olarak işaretlemekten kaçınmak için.

Daha genel sorunuz "C ++ neden Java ile aynı değil?" Sorusu varsa, "Neden Java yerine C ++ yazıyorsunuz?" ;-p

Daha da ciddisi, C ++ derleyicisinin sadece başka bir çeviri birimine ulaşamaması ve sembollerini javac'ın yapabileceği ve yapabileceği şekilde nasıl kullanacağını anlayamaması. Başlık dosyası, derleyiciye bağlantı zamanında ne beklendiğini bildirmek için gereklidir.

Yani #includedüz bir metinsel değiştirmedir. Başlık dosyalarındaki her şeyi tanımlarsanız, önişlemci, projenizdeki her kaynak dosyanın muazzam bir kopyasını ve yapıştırmasını oluşturur ve bunu derleyiciye besler. 1998'de C ++ standardının onaylanmış olması bununla hiçbir ilgisi yoktur, C ++ için derleme ortamının C'ye çok yakın olması gerçeğidir.

Yorumlarımı, takip eden sorunuza cevap verecek şekilde dönüştürme:

Derleyici içindeki kodu içeren .cpp dosyasını nasıl bulur?

En azından üstbilgi dosyasını kullanan kodu derlemez. Bağlandığınız işlevlerin henüz yazılmasına gerek yoktur, derleyicinin hangi .cppdosyada olacağını bilmesini sağlayın . Arama kodunun derleme zamanında bilmesi gereken her şey işlev bildiriminde ifade edilir. Bağlantı sırasında, bir .odosya listesi veya statik veya dinamik kitaplıklar sağlarsınız ve yürürlükteki başlık, işlevlerin tanımlarının orada bir yerde olacağına dair bir vaattir.


3
"Ayırmak isteyebileceğiniz nedenler:" eklemek için & Başlık dosyalarının en önemli işlevi şudur: Kod yapısı tasarımını uygulamadan ayırmak için, Çünkü: A. Birçok nesneyi içeren gerçekten karmaşık yapılara girdiğinizde başlık dosyalarına göz atmak ve başlık yorumlarınızla birlikte nasıl birlikte çalıştıklarını hatırlamak çok daha kolaydır. B.Bir kişi tüm nesne yapısını tanımlamakla ilgilenmezken, bir diğeri de uygulama ile ilgilenir, işleri organize tutar. Her şeyden önce karmaşık kod daha okunabilir yapar düşünüyorum.
Andres Canella

En basit şekilde başlık ve cpp dosyaları ayırma yararlı olduğunu düşünebilirsiniz gerçekten Orta / büyük projeler için yardımcı Arayüz vs Uygulamaları ayırmaktır.
Krishna Oza

Ben (2), "tanımları olmadan kod karşı bağlantı" dahil olması amaçlanmıştır. Tamam, bu yüzden git repo'ya içindeki tanımlarla erişebilirsiniz, ancak nokta, kodunuzu bağımlılıklarının uygulamalarından ayrı olarak derleyebilmeniz ve ardından bağlantı verebilmenizdir. Kelimenin tam anlamıyla istediğiniz tek şey, arayüz / uygulamayı farklı dosyalara ayırmaksa, ayrı bir bina oluşturmaktan endişe duymuyorsanız, bunu sadece foo_interface.h'yi foo_implementation.h'yi içererek yapabilirsiniz.
Steve Jessop

4
@AndresCanella Hayır değil. Kendi kodunuzu okumayı ve korumayı bir kabus haline getirir. Kodda bir şeyin ne yaptığını tam olarak anlamak için n dosyaları yerine 2n dosyalarına atlamanız gerekir. Bu sadece Big-Oh notasyonu değil, 2n sadece n'ye kıyasla çok fazla fark yaratıyor.
Błażej Michalik

1
İkinci olarak, başlıkların yardımcı olduğu bir yalan. örneğin minix kaynağını kontrol edin, kontrolün geçildiği yere, işlerin bildirildiği / tanımlandığı yere kadar gitmek çok zordur .. eğer ayrı dinamik modüller aracılığıyla inşa edilmiş olsaydı, bir şey anlayarak sindirilebilir, sonra atlar bir bağımlılık modülü. bunun yerine, başlıkları takip etmeniz gerekir ve bu şekilde yazılmış herhangi bir kodu okumanızı cehenneme çevirir. aksine, nodejs herhangi bir ifdefs olmadan nereden geldiğini netleştirir ve nereden geldiğini kolayca belirleyebilirsiniz.
Dmitry

91

C ++ bu şekilde yapar çünkü C bu şekilde yapmıştır, bu yüzden asıl soru neden C bu şekilde yapmıştır? Wikipedia bununla ilgili biraz konuşur.

Daha yeni derlenmiş diller (Java, C # gibi) ileri bildirimler kullanmaz; tanımlayıcılar kaynak dosyalardan otomatik olarak tanınır ve doğrudan dinamik kütüphane sembollerinden okunur. Bu, başlık dosyalarına gerek olmadığı anlamına gelir.


13
+1 Kafasına çiviyi vurur. Bu gerçekten ayrıntılı bir açıklama gerektirmez.
MSalters

6
Kafamda çivime çarpmadı :( Hala C ++ 'ın neden ileri bildirimleri kullanması gerektiğini ve neden kaynak dosyalardan tanımlayıcıları tanıyabildiğini ve doğrudan dinamik kütüphane sembollerinden okuyabildiğini araştırmak zorundayım ve neden C ++ bunu yaptı? C bu şekilde yaptığı için: p
Alexander Taylor

3
Ve bunu yaptıktan sonra daha iyi bir programcısın @AlexanderTaylor :)
Donald Byrd

66

Bazı kişiler başlık dosyalarını bir avantaj olarak görür:

  • Arayüz ve uygulamanın ayrılmasını sağladığı / uyguladığı / uyguladığı iddia edilir - ancak genellikle durum böyle değildir. Başlık dosyaları uygulama ayrıntılarıyla doludur (örneğin, ortak arabirimin bir parçası olmasa da, bir sınıfın üye değişkenlerinin başlıkta belirtilmesi gerekir) ve işlevler sınıf bildiriminde satır içi olarak tanımlanabilir ve genellikle tanımlanabilir yine bu ayrılığı yok eder.
  • Bazen derleme süresini geliştirdiği söylenir çünkü her çeviri birimi bağımsız olarak işlenebilir. Ve yine de derleme zamanı söz konusu olduğunda C ++ muhtemelen var olan en yavaş dildir. Nedenin bir kısmı, aynı başlığın birçok tekrarlanan kapanmasıdır. Çok sayıda başlık birden fazla çeviri birimi tarafından dahil edilir ve birden çok kez ayrıştırılması gerekir.

Nihayetinde, başlık sistemi, C'nin tasarlandığı 70'lerden kalma bir eserdir. O zamanlar bilgisayarların belleği çok azdı ve tüm modülü bellekte tutmak bir seçenek değildi. Bir derleyici, dosyayı en üstte okumaya başlamalı ve ardından kaynak kod boyunca doğrusal olarak ilerlemelidir. Başlık mekanizması bunu mümkün kılar. Derleyicinin diğer çeviri birimlerini dikkate alması gerekmez, sadece yukarıdan aşağıya kodu okuması gerekir.

Ve C ++ geriye dönük uyumluluk için bu sistemi korudu.

Bugün hiç mantıklı değil. Verimsiz, hata eğilimli ve aşırı karmaşıktır. Amaç buysa , arayüz ve uygulamayı ayırmanın çok daha iyi yolları vardır .

Bununla birlikte, C ++ 0x için önerilenlerden biri, kodun .NET veya Java'ya benzer şekilde daha büyük modüllere, tek seferde ve üstbilgisiz derlenmesine izin veren uygun bir modül sistemi eklemekti. Bu teklif C ++ 0x'de kesinti yapmadı, ancak hala "bunu daha sonra yapmak isteriz" kategorisinde olduğuna inanıyorum. Belki bir TR2 veya benzeri bir cihazda.


BU sayfadaki en iyi cevaptır. Teşekkür ederim!
Chuck Le Butt

29

Benim (sınırlı - Ben normalde bir C geliştiricisi değilim) anlayışına göre, bu C'ye dayanır. Ayrıca, işlevleri kullanmadan önce bildirilmelidir.

Örneğin, aşağıdakiler bir derleyici hatası vermelidir:

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Hata, bildirimden önce çağırdığınız için "SomeOtherFunction bildirilmedi" olmalıdır. Bunu düzeltmenin bir yolu, SomeOtherFunction işlevini SomeFunction öğesinin üzerine getirmektir. Diğer bir yaklaşım önce işlev imzasını beyan etmektir:

void SomeOtherFunction();

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Bu derleyicinin bilmesini sağlar: Kodda bir yere bakın, voO döndüren ve herhangi bir parametre almayan SomeOtherFunction adlı bir işlev vardır. Bu nedenle, SomeOtherFunction işlevini çağırmaya çalışan kodlayıcı kodunu kullanıyorsanız, panik yapmayın ve bunun yerine onu arayın.

Şimdi, iki farklı .c dosyasında SomeFunction ve SomeOtherFunction olduğunu düşünün. Daha sonra #.c'ye "SomeOther.c" ifadesini dahil etmeniz gerekir. Şimdi SomeOther.c'ye "özel" fonksiyonlar ekleyin. C özel fonksiyonları bilmediğinden, bu fonksiyon Some.c'de de kullanılabilir.

Burada .h Dosyaları devreye girer: Diğer .c dosyalarında erişilebilen bir .c dosyasından 'Vermek' istediğiniz tüm işlevleri (ve değişkenleri) belirtirler. Bu şekilde, Genel / Özel kapsam gibi bir şey kazanırsınız. Ayrıca, bu .h dosyasını kaynak kodunuzu paylaşmak zorunda kalmadan başkalarına verebilirsiniz - .h dosyaları da derlenmiş .lib dosyalarına karşı çalışır.

Bu yüzden asıl neden gerçekten kolaylık sağlamak, kaynak kodu koruması ve uygulamanızın parçaları arasında biraz ayrışmaktır.

Yine de C idi. C ++, Sınıfları ve özel / genel değiştiricileri tanıttı, bu yüzden hala gerekli olup olmadıklarını sorabilirken, C ++ AFAIK hala kullanmadan önce işlevlerin bildirilmesini gerektirir. Ayrıca, birçok C ++ Geliştiricisi de C geliştiricisidir veya C geliştiricisidir ve kavramlarını ve alışkanlıklarını C ++ 'a devraldı - neden kırılmamış olanı değiştirelim?


5
Derleyici kod üzerinden neden çalışamıyor ve tüm fonksiyon tanımlarını bulamıyor? Derleyiciye programlanması oldukça kolay bir şey gibi görünüyor.
Marius

3
Eğer varsa sahip kaynağı, hangi sıklıkla yok. Derlenmiş C ++, kodu yüklemek ve bağlamak için yeterli ek bilgiye sahip etkili bir makine kodudur. Ardından, CPU'yu giriş noktasına doğrultup çalışmasına izin veriyorsunuz. Bu, kodun içeriğinde meta veriler içeren bir ara bayt kodunda derlendiği Java veya C # 'dan temel olarak farklıdır.
DevSolar

1972'de bunun derleyici için oldukça pahalı bir işlem olabileceğini tahmin ediyorum.
Michael Stum

Tam olarak Michael Stum'un söyledikleri. Bu davranış tanımlandığında, tek bir çeviri birimi üzerinden doğrusal bir tarama, pratik olarak uygulanabilecek tek şeydi.
jalf

3
Yup - bant kütlesi torage ile 16 acı üzerinde derlemek önemsiz değildir.
MSalters

11

İlk avantaj: Başlık dosyalarınız yoksa, kaynak dosyaları diğer kaynak dosyalarına dahil etmeniz gerekir. Bu, dahil edilen dosya değiştiğinde dahil edilen dosyaların tekrar derlenmesine neden olur.

İkinci avantaj: Farklı birimler (farklı geliştiriciler, ekipler, şirketler vb.) Arasındaki kodu paylaşmadan arayüzlerin paylaşılmasını sağlar.


1
Örneğin C # 'da kaynak dosyaları diğer kaynak dosyalarına eklemeniz gerekeceğini' mi ima ediyorsunuz? Çünkü belli ki yapmıyorsunuz. İkinci avantaj için, bu çok dile bağlı olduğunu düşünüyorum: örneğin Delphi
Vlagged

Yine de tüm projeyi yeniden derlemelisiniz, bu yüzden ilk avantaj gerçekten önemli mi?
Marius

tamam, ama bir dil özelliği olduğunu sanmıyorum. "Sorun" tanımından önce C deklarasyonuyla uğraşmak daha pratik bir şeydir. Ünlü biri "bu bir özellik değil bir hata değil" demek gibi :) :)
nöro

@Marius: Evet, gerçekten önemli. Tüm projeyi bağlamak, tüm projeyi derlemek ve bağlamaktan farklıdır. Projedeki dosyaların sayısı arttıkça, hepsini derlemek gerçekten sinir bozucu olur. @Vlagged: Haklısın, ama c ++ 'ı başka bir dille karşılaştırmadım. Sadece kaynak dosyaları kullanarak kaynak ve başlık dosyalarını kullanarak karşılaştırdım.
erelender

C # başkalarına kaynak dosyaları içermez, ancak yine de modüllere başvurmanız gerekir ve bu da derleyicinin kodunu kullandığı sembolleri ayrıştırmak için kaynak dosyaları getirmesini (veya ikili dosyaya yansıtmasını) sağlar.
gbjbaanb

5

Üstbilgi dosyalarına duyulan gereksinim, derleyicinin diğer modüllerdeki işlevler ve değişkenler için tür bilgisi hakkında bilgi sahibi olduğu sınırlamalardan kaynaklanır. Derlenmiş program veya kütüphane, derleyicinin diğer derleme birimlerinde tanımlanan herhangi bir nesneye bağlanması için gereken tür bilgilerini içermez.

Bu sınırlamayı telafi etmek için, C ve C ++ bildirimlere izin verir ve bu bildirimler, ön işlemcinin #include yönergesi yardımıyla bunları kullanan modüllere dahil edilebilir.

Diğer yandan Java veya C # gibi diller, derleyicinin çıktısında (sınıf dosyası veya derleme) ciltleme için gerekli bilgileri içerir. Bu nedenle, artık bir modülün müşterileri tarafından dahil edilecek bağımsız bildirimlerin korunmasına gerek yoktur.

Derleyici çıktısında bağlayıcı bilginin bulunmamasının nedeni basittir: çalışma zamanında gerekli değildir (derleme zamanında herhangi bir tür denetimi gerçekleştirilir). Sadece yer harcar. C / C ++ 'nın bir çalıştırılabilir dosya veya kütüphanenin büyüklüğünün biraz önemli olduğu bir zamandan geldiğini unutmayın.


Size katılıyorum. Burada benzer bir fikrim var: stackoverflow.com/questions/3702132/…
smwikipedia

4

C ++, C hakkında özellikle dilin kendisi ile ilgili olmayan hiçbir şeyi gereksiz yere değiştirmeden, C altyapısına modern programlama dili özellikleri eklemek için tasarlanmıştır.

Evet, bu noktada (ilk C ++ standardından 10 yıl sonra ve kullanımda ciddi bir şekilde büyümeye başladıktan 20 yıl sonra) neden uygun bir modül sistemine sahip olmadığını sormak kolaydır. Açıkçası bugün tasarlanan herhangi bir yeni dil C ++ gibi çalışmaz. Ama bu C ++ 'ın amacı değil.

C ++ 'ın amacı evrimsel olmak, mevcut uygulamanın sorunsuz bir şekilde devam etmesi ve yalnızca kullanıcı topluluğu için yeterince çalışan şeyleri (çok sık) bozmadan yeni yetenekler eklemektir.

Bu, bazı şeyleri (özellikle yeni bir projeye başlayan kişiler için) ve bazı şeyleri (özellikle mevcut kodu koruyanlar için) diğer dillerden daha zor hale getirdiği anlamına gelir.

Yani C ++ 'ın (zaten C # olarak anlamsız olurdu) C #' a dönüşmesini beklemek yerine, neden sadece iş için doğru aracı seçmeyesiniz? Kendim, modern bir dilde (C # kullanıyorum) önemli yeni işlevsellik parçaları yazmak için gayret ediyorum ve C ++ 'da tuttuğum mevcut C ++ çok miktarda var, çünkü yeniden yazarken gerçek bir değer olmayacaktı herşey. Zaten çok güzel bir şekilde entegre oluyorlar, bu yüzden büyük ölçüde ağrısız.


C # ve C ++ 'ı nasıl entegre edersiniz? COM ile mi?
Peter Mortensen

1
Üç ana yol vardır, "en iyisi" mevcut kodunuza bağlıdır. Üçünü de kullandım. En çok kullandığım COM, çünkü mevcut kodum zaten etrafında tasarlanmıştı, bu yüzden pratik olarak sorunsuz, benim için çok iyi çalışıyor. Bazı garip yerlerde, C ++ / CLI kullanıyorum, bu da COM arayüzlerine sahip olmadığınız herhangi bir durum için inanılmaz derecede pürüzsüz entegrasyon sağlıyor (ve eğer sahipseniz bile mevcut COM arayüzlerini kullanmayı tercih edebilirsiniz). Son olarak, temelde bir DLL'den maruz kalan herhangi bir C benzeri işlevi çağırmanıza izin veren p / invoke vardır, bu yüzden doğrudan C #'dan herhangi bir Win32 API'yi çağırmanıza izin verir.
Daniel Earwicker

4

C ++, 1998'de onaylandı, ancak bundan çok daha uzun bir süredir kullanımdaydı ve onaylama, yapıyı dayatmak yerine öncelikle mevcut kullanımı ayarlıyordu. C ++ C'ye dayandığından ve C'nin başlık dosyaları olduğundan, C ++ da onlara sahiptir.

Başlık dosyalarının ana nedeni, dosyaların ayrı olarak derlenmesini sağlamak ve bağımlılıkları en aza indirmektir.

Diyelim ki foo.cpp var ve bar.h / bar.cpp dosyalarından kod kullanmak istiyorum.

Ben foo.cpp içinde "bar.h" ekleyebilir ve bar.cpp olmasa bile foo.cpp programlayabilir ve derleyebilirim. Başlık dosyası, derleyiciye bar.h dosyasındaki sınıfların / işlevlerin çalışma zamanında var olacağına dair bir vaat görevi görür ve zaten bilmesi gereken her şeye sahiptir.

Tabii ki, bar.h içindeki işlevler programımı bağlamaya çalıştığımda gövdeler yoksa, o zaman bağlantı oluşturmaz ve bir hata alırım.

Yan etki, kaynak kodunuzu açıklamadan kullanıcılara bir başlık dosyası verebilmenizdir.

Başka bir deyişle, kodunuzun * .cpp dosyasında uygulanmasını değiştirirseniz, ancak üstbilgiyi hiç değiştirmezseniz, onu kullanan her şey yerine yalnızca * .cpp dosyasını derlemeniz gerekir. Tabii ki, başlık dosyasına çok fazla uygulama koyarsanız, bu daha az yararlı olur.


3

Ana işlevle aynı işlevlere sahip ayrı bir başlık dosyasına ihtiyaç duymaz. Yalnızca birden çok kod dosyası kullanarak bir uygulama geliştirirseniz ve daha önce bildirilmemiş bir işlevi kullanırsanız buna ihtiyaç duyar.

Bu gerçekten bir kapsam sorunu.


1

C ++ 1998 yılında onaylandı, neden bu şekilde tasarlandı? Ayrı bir başlık dosyasına sahip olmanın ne gibi avantajları vardır?

Aslında, başlık dosyaları programları ilk kez incelerken çok faydalı hale gelir, başlık dosyalarını kontrol etmek (yalnızca bir metin düzenleyicisi kullanarak), sınıfları görüntülemek için gelişmiş araçları kullanmanız gereken diğer dillerden farklı olarak, programın mimarisine genel bir bakış sağlar. üye işlevleri.


1

Gerçek başlık dosyaları arkasında (tarihi) nedeni derleyici geliştiriciler için daha kolay gibi yapıyordu düşünüyorum ... ama sonra, başlık dosyaları yok avantajlar verir.
Kontrol bu önceki posta daha tartışmalar için ...


1

Üstbilgi dosyaları olmadan mükemmel bir şekilde C ++ geliştirebilirsiniz. Aslında, şablonları yoğun olarak kullanan bazı kütüphaneler başlık / kod dosyaları paradigmasını kullanmazlar (bkz. Destek). Ancak C / C ++ 'da bildirilmemiş bir şeyi kullanamazsınız. Bununla başa çıkmanın pratik bir yolu başlık dosyalarını kullanmaktır. Ayrıca, kod / uygulama paylaşmadan arayüz paylaşma avantajı elde edersiniz. Ve sanırım C yaratıcıları tarafından tasarlanmadı: Paylaşılan başlık dosyalarını kullandığınızda ünlüleri kullanmanız gerekir:

#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN

// [...]
// my header
// [...]

#endif // MY_HEADER_SWEET_GUARDIAN

bu gerçekten bir dil özelliği değil, birden fazla dahil etme ile başa çıkmanın pratik bir yoludur.

Bu yüzden, C yaratıldığında, ileri bildirimle ilgili sorunların hafife alındığını ve şimdi C ++ gibi üst düzey bir dil kullanırken bu tür şeylerle uğraşmak zorunda olduğumuzu düşünüyorum.

Zavallı C ++ kullanıcıları için başka bir yük ...


1

Derleyicinin diğer dosyalarda tanımlanan sembolleri otomatik olarak bulmasını istiyorsanız, programcıyı bu dosyaları önceden tanımlanmış konumlara yerleştirmeye zorlamanız gerekir (Java paketleri yapısı projenin klasör yapısını belirler gibi). Başlık dosyalarını tercih ederim. Ayrıca, kullandığınız kütüphane kaynaklarına veya derleyicinin ihtiyaç duyduğu bilgileri ikili dosyalara yerleştirmek için tek tip bir yola ihtiyacınız olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.