Neden her şey yalnızca .cpp dosyasını eklerken çalışırken .h'yi eklememiz gerekiyor?


18

Neden biz de dahil etmek gerekiyor .hve .cppbiz dahil ederek bunu yalnızca çalışmasını sağlayacak sırasında dosyaları .cppdosyasını?

Örneğin: file.hiçeren bildirimler oluşturma, ardından file.cppiçeren tanımlar oluşturma ve her ikisini de içine alma main.cpp.

Alternatif olarak: file.cppİçinde içeren bir bildiri / tanım oluşturma (prototip yok) main.cpp.

İkisi de benim için çalışıyor. Farkı göremiyorum. Belki derleme ve bağlama süreci hakkında bir fikir verebilir.


Araştırmanızı paylaşmak herkese yardımcı olur . Neyi denediğinizi ve neden ihtiyaçlarınızı karşılamadığını bize bildirin. Bu, kendinize yardım etmeye zaman ayırdığınızı gösterir, bizi açık cevapları tekrar etmekten kurtarır ve en önemlisi daha spesifik ve ilgili bir cevap almanıza yardımcı olur. Ayrıca bkz. Nasıl
Sorulur

Yandaki ilgili sorulara bakmanızı şiddetle tavsiye ederim. programmers.stackexchange.com/questions/56215/… ve programmers.stackexchange.com/questions/115368/… iyi okumalardır

Neden SO'da değil Programcılar'da?
Monica ile Hafiflik Yarışları

@LightnessRacesInOrbit çünkü bu bir "nasıl yapılır" sorusu değil, bir kodlama stili meselesidir.
CashCow

@CashCow: Bir "nasıl yapılır" sitesi olmayan SO'yu yanlış anladığınız anlaşılıyor . Ayrıca , kodlama stili sorunu olmayan bu soruyu yanlış anladığınız anlaşılıyor .
Monica ile Hafiflik Yarışları

Yanıtlar:


29

Eğer iken edebilir dahil.cpp Bahsettiğiniz gibi dosyaları, bu kötü bir fikir.

Bahsettiğiniz gibi, bildirimler başlık dosyalarına aittir. Bunlar, uygulamaları içermediği için çoklu derleme birimlerine dahil edildiğinde sorun yaratmaz. Bir işlev veya sınıf üyesinin tanımını birden çok kez eklemek normalde bir soruna neden olur (ancak her zaman değil) çünkü bağlayıcı karışır ve bir hata atar.

Ne olmalı her biri .cpp dosya programın bir alt kümesi için sınıf, mantıksal olarak organize edilmiş fonksiyonlar grubu, global statik değişkenler (hiç değilse az kullanılır) vb.

Her derleme birimi (.cpp dosya) daha sonra içerdiği tanımları derlemek için gereken bildirimleri içerir. Başvurduğu ancak içermediği işlevleri ve sınıfları izler, böylece bağlayıcı daha sonra nesne kodunu bir yürütülebilir dosya veya kitaplıkla birleştirdiğinde bunları çözebilir.

Misal

  • Foo.h -> Foo sınıfı için bildirim (arayüz) içerir.
  • Foo.cpp -> Foo sınıfı için tanım (uygulama) içerir.
  • Main.cpp-> ana yöntemi, program giriş noktasını içerir. Bu kod bir Foo başlatır ve kullanır.

Hem Foo.cppve Main.cppihtiyaç içerecek şekilde Foo.h. Foo.cppsınıf arayüzünü destekleyen kodu tanımladığı için buna ihtiyaç duyar, bu yüzden bu arayüzün ne olduğunu bilmelidir.Main.cppbuna bir Foo yarattığı ve davranışını çağırdığı için ihtiyaç duyuyor, bu yüzden bu davranışın ne olduğunu, bir Foo'nun hafızadaki boyutunu ve işlevlerini nasıl bulabileceğini vb. bilmeli ama henüz gerçek uygulamaya ihtiyacı yok.

Derleyici üretecektir Foo.ogelen Foo.cppderlenmiş biçimde Foo sınıf kodu tüm ihtiva etmektedir. Ayrıca Main.o, ana yöntemi ve sınıf Foo'ya çözülmemiş referansları içeren üretir .

Şimdi iki nesne dosyasını Foo.ove Main.oyürütülebilir bir dosyada birleştiren bağlayıcı geliyor . Çözümlenmemiş Foo referanslarını Main.ogörüyor, ancak Foo.ogerekli sembolleri içeriyor, bu yüzden "noktaları birleştiriyor". Bir işlev çağrısı Main.oartık derlenen kodun gerçek konumuna bağlanır, böylece çalışma zamanında program doğru konuma atlayabilir.

Foo.cppDosyayı içine dahil etseydiniz, Foo sınıfının iki tanımı Main.cppolurdu . Bağlayıcı bunu görür ve "Hangisini seçeceğimi bilmiyorum, bu yüzden bu bir hata." Derleme adımı başarılı olur, ancak bağlantı kurulamaz. (Derlemediğiniz sürece neden ayrı bir dosyadadır?)Foo.cpp.cpp

Son olarak, farklı dosya türleri fikri bir C / C ++ derleyicisiyle alakasızdır. İstenen dil için geçerli kod içeren "metin dosyalarını" derler. Bazen dili, dosya uzantısına göre söyleyebilir. Örneğin, .cderleyici seçeneği olmayan bir dosyayı derleyin ve C .ccveya .cppvarsayalım, C ++ olduğunu varsayar. Ancak, bir derleyiciye C ++ olarak bir dosyayı .hbile derlemesini söyleyebilirim .docxve .odüz metin biçiminde geçerli C ++ kodu içeriyorsa bir object ( ) dosyası yayar . Bu uzantılar daha çok programcının yararınadır. Eğer görürsem Foo.hve Foo.cppderhal ilkinin sınıfın bildirisini, ikincisinin de tanımı içerdiğini varsayarım.


Burada yaptığınız argümanı anlamıyorum. Sadece eklerseniz Foo.cppiçinde Main.cppeklemek gerekmez .hdosyası, hala okunabilmesi için ayrı dosyalar halinde bölme kodu kazanmak, bir eksik dosya varsa ve derleme komuta basittir. Başlık dosyalarının önemini anlıyorum, bunun böyle olduğunu sanmıyorum
Benjamin Gruenbaum

2
Önemsiz bir program için, hepsini tek bir dosyaya koyun. Herhangi bir proje başlığı bile eklemeyin (sadece kütüphane başlıkları). Kaynak dosyaları dahil etmek, birden fazla derleme biriminiz olduğunda ve bunları ayrı olarak derledikten sonra en küçük programlardan başka sorunlara neden olacak kötü bir alışkanlıktır.

2
If you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.Soru ile ilgili en önemli cümle.
marczellm

@Snowman Doğru, odaklanmanız gerektiğini düşündüğüm şey bu - çoklu derleme birimleri. Daha küçük parçalara kadar kod kırmak için üstbilgi dosyaları içeren / içermeyen kod yapıştırmak, üstbilgi dosyalarının amacı için faydalı ve dikeydir. Tek yapmak istediğimiz dosya başlık dosyalarına böldüğümüzde bize hiçbir şey satın almazsınız - dinamik bağlantıya, koşullu bağlantıya ve daha gelişmiş yapılara ihtiyaç duyduğumuzda aniden çok çok faydalı olurlar.
Benjamin Gruenbaum

Bir C / C ++ derleyicisi / bağlayıcısı için kaynak düzenlemenin birçok yolu vardır. Asker süreçten emin değildi, bu yüzden kodu nasıl organize edeceğimize ve sürecin nasıl çalıştığına dair en iyi uygulamayı açıkladım. Kodları farklı bir şekilde organize etmenin mümkün olduğu konusunda saçları bölebilirsiniz, ancak gerçek şu ki, orada projelerin% 99'unun bunu bir sebeple en iyi uygulama olan nasıl yaptığını açıkladım. İyi çalışıyor ve aklını koruyor.

9

Kavramsal olarak C veya C ++ derleyicisinin ilk "aşaması" olan C ve C ++ önişlemcisinin rolü hakkında daha fazla bilgi edinin (tarihsel olarak ayrı bir programdı /lib/cpp; şimdi, performans nedenlerinden dolayı derleyici içine uygun şekilde entegre edilmiştir cc1 veya cc1plus). Özellikle GNU cppön işlemcisinin belgelerini okuyun . Bu yüzden pratikte derleyici kavramsal olarak önce derleme biriminizi (veya çeviri biriminizi ) önceden işler ve daha sonra önceden işlenmiş form üzerinde çalışır.

Muhtemelen başlık dosyasını içeriyorsa ( kurallar ve alışkanlıklar tarafından belirlendiği şekilde) her zaman dahil etmeniz gerekir :file.h

  • makro tanımları
  • tür tanımları (ör typedef. struct, classvb.)
  • static inlinefonksiyonların tanımları
  • dış fonksiyon bildirimleri.

Bunları bir başlık dosyasına koymanın bir kural (ve kolaylık) konusu olduğuna dikkat edin.

Tabii ki, uygulamanızın file.cppyukarıdakilerin hepsine ihtiyacı var, bu yüzden #include "file.h" ilk başta istiyor .

Bu bir kongre (ama çok yaygın). Başlık dosyalarından kaçınabilir ve içeriklerini uygulama dosyalarına (yani çeviri birimleri) kopyalayıp yapıştırabilirsiniz. Ancak bunu istemezsiniz (belki C veya C ++ kodunuz otomatik olarak oluşturulursa; o zaman jeneratör programını o kopyala ve yapıştır yaparak ön işlemcinin rolünü taklit ederek yapabilirsiniz).

Mesele şu ki önişlemci sadece metinsel işlemler yapıyor . (Prensip olarak) tamamen kopyalayıp yapıştırarak önleyebilir veya başka bir "önişlemci" veya C kodu üreticisi ( gpp veya m4 gibi ) ile değiştirebilirsiniz.

Ek bir sorun, son C (veya C ++) standartlarının birkaç standart başlık tanımlamasıdır. Çoğu uygulama gerçekten bu standart başlıkları (uygulamaya özel) dosyalar olarak uygular , ancak uygun bir uygulamanın bazı sihirli hilelerle (örneğin, bazı veritabanlarını veya bazılarını kullanarak) standart içerikler ( #include <stdio.h>C veya #include <vector>C ++ için) uygulamasının mümkün olabileceğine inanıyorum. derleyici içindeki bilgiler ).

Kullanılıyorsa GCC derleyicileri (örneğin gccveya g++) kullanabilirsiniz -Hher eklenmesi hakkında bilgi almak için bayrak ve -C -Ebayraklar ön işlenmiş formu elde etmek. Tabii ki önişlemeyi etkileyen birçok derleyici bayrağı vardır (örneğin -I /some/dir/, /some/dir/dahil edilen dosyaları aramak için eklemek ve -Dbazı önişlemci makrosu vb. Önceden tanımlamak için).

NB. C ++ 'ın gelecekteki sürümlerinde (belki C ++ 20 , belki de daha sonra) C ++ modülleri olabilir .


1
Bağlantılar çürürse, bu hala iyi bir cevap olur mu?
Dan Pichelman

4
Cevabımı geliştirmek için düzenledim ve uzun süre alakalı kalacağına inandığım wikipedia'ya veya GNU belgeseline bağlantılar seçiyorum.
Basile Starynkevitch

3

C ++ 'ın çok birimli derleme modeli nedeniyle, programınızda yalnızca bir kez görünen kodlara sahip olmanın bir yoluna ihtiyacınız vardır (tanımlar) ve programınızın her çeviri biriminde görünen kodlara sahip olmanın bir yoluna ihtiyacınız vardır (bildirimler).

Bundan C ++ başlık deyimi doğar. Bir sebepten dolayı kongre.

Sen olabilir tek çeviri birimine bütün planlarını dökümü, ancak kod yeniden kullanımı, birim test ve modüller arası bağımlılık işleme ile bu tanıtır problemleri. Aynı zamanda büyük bir karmaşa.


0

Seçilen cevabı Neden bir başlık dosyası yazmak gerekiyor? makul bir açıklama, ancak ek ayrıntı eklemek istedim.

Başlık dosyaları için rasyonel C / C ++ öğretiminde ve tartışmasında kaybolma eğiliminde gibi görünüyor.

Üstbilgiler dosyası iki uygulama geliştirme sorununa bir çözüm sağlar:

  1. Arayüz ve uygulamanın ayrılması
  2. Büyük programlar için iyileştirilmiş derleme / bağlantı süreleri

C / C ++ küçük programlardan çok büyük milyonlarca satırlık, bin dosya programlarına kadar ölçeklendirilebilir. Uygulama geliştirme, bir geliştiricinin ekiplerinden yüzlerce geliştiriciye kadar ölçeklendirilebilir.

Bir geliştirici olarak birkaç şapka takabilirsiniz. Özellikle, fonksiyonlar ve sınıflar için bir arayüzün kullanıcısı olabilir veya fonksiyonlar ve sınıflar için bir arayüzün yazarı olabilirsiniz.

Bir işlevi kullanırken, işlev arabirimini, hangi parametreleri kullanacağınızı, işlevlerin geri döndüğünü ve işlevin ne yaptığını bilmeniz gerekir. Bu, uygulamaya hiç bakmadan başlık dosyasında kolayca belgelenir. Uygulamasını ne zaman okudunuz printf? Her gün kullanıyoruz.

Bir arayüzün geliştiricisi olduğunuzda, şapka diğer yönü değiştirir. Üstbilgi dosyası genel arabirimin bildirimini sağlar. Başlık dosyası, bu arayüzü kullanmak için başka bir uygulamanın neye ihtiyacı olduğunu tanımlar. Bu yeni arayüze ait dahili ve özel bilgiler başlık dosyasında bildirilmez (ve bildirilmemelidir). Herkese açık üstbilgi dosyası herkesin modülü kullanması gerekir.

Büyük ölçekli geliştirme için, derleme ve bağlama uzun zaman alabilir. Birçok dakikadan saatlere (hatta günlerce!). Yazılımı arayüzlere (başlıklara) ve uygulamalara (kaynaklara) bölmek, her şeyi yeniden oluşturmak yerine yalnızca derlenmesi gereken dosyaları derlemek için bir yöntem sağlar.

Ayrıca, üstbilgi dosyaları bir geliştiricinin bir üstbilginin yanı sıra bir kitaplık (önceden derlenmiş) sağlamasına olanak tanır. Kütüphanenin diğer kullanıcıları uygulamayı düzgün görmeyebilir, ancak yine de kütüphaneyi başlık dosyasıyla birlikte kullanabilir. Bunu her gün C / C ++ standart kütüphanesi ile yaparsınız.

Küçük bir uygulama geliştiriyor olsanız bile, büyük ölçekli yazılım geliştirme tekniklerini kullanmak iyi bir alışkanlıktır. Ancak bu alışkanlıkları neden kullandığımızı da hatırlamamız gerekiyor.


0

Tüm kodunuzu neden tek bir dosyaya koymayacağınızı da soruyor olabilirsiniz.

En basit cevap kod bakımıdır.

Sınıf oluşturmanın makul olduğu zamanlar vardır:

  • tümü bir başlık içinde satır içi
  • bir derleme birimi içinde ve hiç başlık yok.

Bir başlıkta tamamen satır içi olmanın makul olduğu zaman, sınıfın gerçekten birkaç temel alıcı ve ayarlayıcıya sahip bir veri yapısı ve belki de üyelerini başlatmak için değer alan bir kurucu olduğu zamandır.

(Tümünün satır içinde olması gereken şablonlar biraz farklı bir konudur).

Üstbilgi içinde bir sınıf oluşturmak için diğer zaman, sınıfı birden çok projede kullanabileceğiniz ve özellikle kütüphanelerde bağlantı kurmaktan kaçınmak istediğiniz zamandır.

Derleme birimine bir sınıfın tamamını dahil edebileceğiniz ve üstbilgisini hiç göstermeyebileceğiniz bir zaman:

  • Yalnızca uyguladığı sınıf tarafından kullanılan bir "impl" sınıfı. Bu sınıfın bir uygulama detayıdır ve harici olarak kullanılmaz.

  • Temel sınıfa bir işaretçi / başvuru / akıllı işaretçi döndüren bir tür fabrika yöntemi tarafından oluşturulan soyut bir temel sınıf uygulaması. Fabrika metodu sınıfın kendisi tarafından açığa çıkmazdı. (Ayrıca, sınıfın kendisini statik bir örnek aracılığıyla bir tabloya kaydeden bir örneği varsa, bir fabrika aracılığıyla gösterilmesine bile gerek yoktur).

  • Bir "işlev" türü sınıfı.

Başka bir deyişle, kimsenin başlığı eklemesini istemediğiniz yer.

Ne düşündüğünüzü biliyorum ... Sadece sürdürülebilirlik için ise, cpp dosyasını (veya tamamen eğimli bir üstbilgi) ekleyerek, kodu "bulmak" ve sadece yeniden oluşturmak için kolayca bir dosyayı düzenleyebilirsiniz.

Ancak "sürdürülebilirlik" sadece kodun düzenli görünmesini sağlamaz. Bu, değişimin etkileri meselesidir. Genel olarak, bir başlığı değişmeden tutarsanız ve yalnızca bir uygulama (.cpp)dosyasını değiştirirseniz , herhangi bir yan etki olmaması gerektiğinden diğer kaynağın yeniden oluşturulması gerekmeyecektir.

Bu, vuruntu etkisi hakkında endişelenmeden bu tür değişiklikleri yapmayı "daha güvenli" hale getirir ve "sürdürülebilirlik" ile gerçekten kastedilen budur.


-1

Snowman'ın örneğinde, .h dosyalarının neden gerekli olduğunu göstermek için küçük bir uzantı gerekir.

Oyuna Foo sınıfına da bağlı olan başka bir Bar sınıfı ekleyin.

Foo.h -> sınıf Foo için bildirim içerir

Foo.cpp -> Foo sınıfının tanımını (impementation) içeriyor

Main.cpp -> Foo türünde bir değişken kullanır.

Bar.cpp -> Foo türünde değişken de kullanır.

Şimdi tüm cpp dosyaları Foo.h içermelidir. Foo.cpp birden fazla cpp dosyasına dahil etmek bir hata olacaktır. Bağlayıcı başarısız olur çünkü Foo sınıfı bir kereden fazla tanımlanır.


1
Bu da değil. Bu, .hyine de dosyalarda çözüldüğü gibi çözülebilir - korumaları içerir ve .hdosyalardaki sorunla aynıdır . .hDosyaların hepsi bu değildir .
Benjamin Gruenbaum

Ben sadece dosya başına (çünkü önişlem yapar gibi) çalışır koruyucular dahil korumaları kullanmak istiyorsunuz emin değilim. Bu durumda Main.cpp ve Bar.cpp farklı dosyalar olduğundan Foo.cpp her ikisine de sadece bir kez dahil edilecektir (korumalı veya fark yaratmaz). Main.cpp ve Bar.cpp derlenir. Ancak bağlantı hatası, Foo sınıfının çift tanımı nedeniyle hala oluşuyor. Ancak bahsetmediğim miras da var. Harici modüller (
dll'ler

Hayır, bir derleme birimi olurdu, .cppdosyayı dahil ettiğiniz için hiçbir bağlantı gerçekleşmeyecekti .
Benjamin Gruenbaum

Derlemeyeceğini söylemedim. Ancak main.o ve bar.o dosyalarını aynı yürütülebilir dosyaya bağlamaz. Bağlantı kurmadan derlemenin ne anlamı var.
SkaarjFace

Ahh ... kodun çalýţmasý mý? Yine de bağlayabilirsiniz, ancak dosyaları birbirine bağlamazsınız - aynı derleme birimi olurdu.
Benjamin Gruenbaum

-2

Tüm kodu aynı dosyaya yazarsanız, kodunuzu çirkin kılacaktır.
İkincisi, yazılı sınıfınızı başkalarıyla paylaşamayacaksınız. Yazılım mühendisliğine göre istemci kodunu ayrıca yazmalısınız. İstemci, programınızın nasıl çalıştığını bilmemelidir. Sadece çıktıya ihtiyaçları var Tüm programı aynı dosyaya yazarsanız, programınızın güvenliğini sızdırı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.