C ++ 'ın neden başlık dosyaları ve .cpp dosyaları var?
C ++ 'ın neden başlık dosyaları ve .cpp dosyaları var?
Yanıtlar:
Temel sebep, arayüzü uygulamadan ayırmak olacaktır. Üstbilgi, bir sınıfın (ya da uygulanmakta olan her şeyin) "ne" yapacağını, cpp dosyası ise bu özellikleri nasıl "gerçekleştireceğini" tanımlar.
Bu, bağımlılıkları azaltır, böylece üstbilgiyi kullanan kodun uygulamanın tüm ayrıntılarını ve yalnızca bunun için gereken diğer sınıfları / başlıkları bilmesi gerekmez. Bu, derleme sürelerini ve ayrıca uygulamadaki bir şey değiştiğinde gereken yeniden derleme miktarını azaltacaktır.
Mükemmel değil ve genellikle arayüzü ve uygulamayı düzgün bir şekilde ayırmak için Pimpl Deyim gibi tekniklere başvurursunuz , ancak iyi bir başlangıçtır.
C ++ 'da bir derleme 2 ana aşamada yapılır:
Birincisi, "kaynak" metin dosyalarının ikili "nesne" dosyalarına derlenmesidir: CPP dosyası derlenmiş dosyadır ve ham bildirim veya üstbilgi dahil etme. CPP dosyası genellikle bir .OBJ veya .O "nesne" dosyasına derlenir.
İkincisi, tüm "nesne" dosyalarının birbirine bağlanması ve böylece nihai ikili dosyanın (bir kütüphane veya yürütülebilir bir dosya) oluşturulmasıdır.
HES tüm bu süreçlere nerede uyar?
Her CPP dosyasının derlenmesi diğer tüm CPP dosyalarından bağımsızdır, yani A.CPP'nin B.CPP'de tanımlanan bir sembole ihtiyacı varsa, örneğin:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
Derlenmeyecektir, çünkü A.CPP'nin "doSomethingElse" varlığını bilmenin bir yolu yoktur ... A.CPP'de aşağıdaki gibi bir bildirim olmadığı sürece:
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
Ardından, aynı sembolü kullanan C.CPP'niz varsa, bildirimi kopyalayıp yapıştırın ...
Evet, bir sorun var. Kopyalama / yapıştırma işlemleri tehlikelidir ve bakımı zordur. Bu da, kopyalamak / yapıştırmak için bir yolumuz olmasaydı ve yine de sembolü ilan etmenin harika olacağı anlamına gelir ... Nasıl yapabiliriz? .H, .hxx, .h ++ veya C ++ dosyaları için tercih ettiğim, .hpp tarafından eklenmiş bazı metin dosyalarının eklenmesiyle:
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
include
çalışır?Bir dosya eklemek, özünde ayrıştırılır ve ardından içeriğini CPP dosyasına kopyalayıp yapıştırır.
Örneğin, aşağıdaki kodda, A.HPP üstbilgisiyle:
// A.HPP
void someFunction();
void someOtherFunction();
... kaynak B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
... dahil edildikten sonra olacak:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
Mevcut durumda, bu gerekli değildir ve B.HPP doSomethingElse
işlev bildirimine sahiptir ve B.CPP doSomethingElse
işlev tanımına sahiptir (kendi başına bir bildirimdir). Ancak, B.HPP'nin bildirimler (ve satır içi kod) için kullanıldığı daha genel bir durumda, karşılık gelen bir tanım olmayabilir (örneğin, numaralandırmalar, düz yapılar vb.), Bu nedenle B.CPP ise içerme gerekebilir B.HPP'den gelen bu beyanı kullanır. Sonuçta, bir kaynağın varsayılan olarak başlığını içermesi "iyi bir tattır".
Bu nedenle başlık dosyası gereklidir, çünkü C ++ derleyicisi yalnızca sembol bildirimlerini arayamaz ve bu nedenle bu bildirimleri ekleyerek yardımcı olmanız gerekir.
Son bir kelime: Birden fazla kapanmanın hiçbir şeyi bozmayacağından emin olmak için HPP dosyalarınızın içeriğine başlık korumaları koymalısınız, ama sonuçta, HPP dosyalarının varlığının ana nedeninin yukarıda açıklandığına inanıyorum.
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
hatta daha basit
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
Gerek yok. CPP HPP'yi "içerdiği sürece", ön derleyici otomatik olarak HPP dosyasının içeriğini CPP dosyasına kopyalayıp yapıştıracaktır. Cevabı açıklığa kavuşturmak için güncelledim.
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. Hayır. Sadece kullanıcı tarafından sağlanan türleri bilir, bu da yarı zamanın geri dönüş değerini okumak için bile uğraşmaz. Ardından, örtük dönüşümler gerçekleşir. Ve sonra, koda sahip olduğunuzda: bir işlev foo(bar)
olduğundan bile emin olamazsınız foo
. Bu yüzden derleyici, kaynağın doğru bir şekilde derlenip derlenmeyeceğine karar vermek için başlıklardaki bilgilere erişmelidir ... Daha sonra, kod derlendiğinde, bağlayıcı işlev çağrılarını birbirine bağlayacaktır.
Seems, they're just a pretty ugly arbitrary design.
: C ++ 2012'de oluşturulmuş olsaydı gerçekten. Ancak, C ++ 'ın 1980'lerde C üzerine kurulduğunu ve o sırada kısıtlamaların o zamanlar oldukça farklı olduğunu unutmayın (IIRC, C'lerden aynı bağlayıcıları tutmaya karar verilmesi).
foo(bar)
bir işlev - eğer bir işaretçi olarak elde edilirse? Aslında, kötü tasarımdan bahsetmişken, C ++ değil C'yi suçluyorum. Girişte birden fazla argüman alırken, başlık dosyalarına sahip olmak veya işlevlerin bir ve sadece bir değer döndürmesi gibi saf C'nin bazı kısıtlamalarını gerçekten sevmiyorum (giriş ve çıkışın benzer şekilde davranması doğal hissetmiyor mu? ; neden birden fazla argüman, ama tek çıktı?) :)
Why can't I be sure, that foo(bar) is a function
foo bir tür olabilir, bu yüzden adlı bir sınıf oluşturucunuz olur. In fact, speaking of bad design, I blame C, not C++
: Bir çok şey için C'yi suçlayabilirim, ancak 70'lerde tasarlanmış olmak bunlardan biri olmayacak. Yine, o zamanın kısıtlamaları ... such as having header files or having functions return one and only one value
: Tuples bunu hafifletmeye ve argümanları referans olarak geçirmeye yardımcı olabilir. Şimdi, döndürülen birden çok değeri almak için sözdizimi ne olurdu ve dili değiştirmeye değer mi?
Kavramın ortaya çıktığı C 30 yaşında olduğu ve o zamandan beri, birden fazla dosyadan kodu birbirine bağlamanın tek geçerli yolu oldu.
Bugün, C ++ 'da derleme süresini tamamen yok eden, sayısız gereksiz bağımlılığa neden olan korkunç bir hack'tir (çünkü bir başlık dosyasındaki sınıf tanımları uygulama hakkında çok fazla bilgi ortaya çıkarır, vb.)
Çünkü C ++ onları C'den miras aldı. Ne yazık ki.
Çünkü kütüphane formatını tasarlayanlar, C önişlemci makroları ve fonksiyon bildirimleri gibi nadiren kullanılan bilgiler için yer kaybetmek istemiyorlardı.
Derleyicinize "bu işlev daha sonra bağlayıcı işi yaparken kullanılabilir" demek için bu bilgilere ihtiyaç duyduğunuzdan, bu paylaşılan bilgilerin depolanabileceği ikinci bir dosya bulmaları gerekiyordu.
C / C ++ 'dan sonraki çoğu dil bu bilgileri çıktıda depolar (örneğin Java bayt kodu) veya önceden derlenmiş bir format kullanmazlar, her zaman kaynak formda dağıtılır ve anında derlenirler (Python, Perl).
Arabirimleri bildirmenin önişlemci yolu. Arabirimi (yöntem bildirimleri) başlık dosyasına ve uygulamayı cpp'ye koydunuz. Kitaplığınızı kullanan uygulamaların yalnızca #include aracılığıyla erişebilecekleri arayüzü bilmeleri gerekir.
Genellikle tüm kodu göndermeden bir arayüz tanımına sahip olmak istersiniz. Örneğin, paylaşılan bir kitaplığınız varsa, onunla paylaşılan kitaplıkta kullanılan tüm işlevleri ve sembolleri tanımlayan bir başlık dosyası gönderirsiniz. Başlık dosyaları olmadan, kaynağı göndermeniz gerekir.
Tek bir projede, IMHO başlık dosyaları en az iki amaç için kullanılır:
Yanıt MadKeithV cevabı ,
Bu, bağımlılıkları azaltır, böylece üstbilgiyi kullanan kodun uygulamanın tüm ayrıntılarını ve yalnızca bunun için gereken diğer sınıfları / başlıkları bilmesi gerekmez. Bu, derleme sürelerini ve ayrıca uygulamadaki bir şey değiştiğinde gereken yeniden derleme miktarını azaltacaktır.
Başka bir neden, bir başlığın her sınıfa benzersiz bir kimlik vermesidir.
Yani bizim gibi bir şeyimiz varsa
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
Projeyi inşa etmeye çalıştığımızda hatalarımız olacak, A B'nin bir parçası olduğundan, başlıklar ile bu tür baş ağrısından kaçınırız ...