2013-08-30 12 views
20

Czas na myślenie - dlaczego mimo to chcesz podzielić swój plik?

Jak sugeruje tytuł, moim największym problemem są błędy linkera definicji. Naprawiłem problem, ale nie naprawiłem problemu we właściwy sposób. Przed rozpoczęciem chcę omówić powody podziału pliku klasy na wiele plików. Starałem się umieścić tutaj wszystkie możliwe scenariusze - jeśli przegapiłem którekolwiek z nich, proszę przypomnieć mi i mogę wprowadzić zmiany. Mam nadzieję, że naśladują poprawne:Oddzielając kod klasy C++ na wiele plików, jakie są zasady?

Powód 1Aby zaoszczędzić miejsce:

Masz plik zawierający deklarację klasy ze wszystkich członków klasy. Umieszczasz #include guards wokół tego pliku (lub #pragma raz), aby uniknąć konfliktów, jeśli #include plik w dwóch różnych plikach nagłówkowych, które są następnie zawarte w pliku źródłowym. Kompilujesz oddzielny plik źródłowy wraz z implementacją dowolnych metod zadeklarowanych w tej klasie, ponieważ wyłącza on ładowanie wielu linii kodu z pliku źródłowego, co nieco oczyszcza i wprowadza porządek do twojego programu.

Przykład: Jak widać, poniższy przykład można poprawić, dzieląc implementację metod klasy na inny plik. (Plik .cpp)

// my_class.hpp 
#pragma once 

class my_class 
{ 
public: 
    void my_function() 
    { 
     // LOTS OF CODE 
     // CONFUSING TO DEBUG 
     // LOTS OF CODE 
     // DISORGANIZED AND DISTRACTING 
     // LOTS OF CODE 
     // LOOKS HORRIBLE 
     // LOTS OF CODE 
     // VERY MESSY 
     // LOTS OF CODE 
    } 

    // MANY OTHER METHODS 
    // MEANS VERY LARGE FILE WITH LOTS OF LINES OF CODE 
} 

Powód 2Aby uniknąć wielu błędów definicja linkera:

Być może to jest główny powód, dla którego chcesz podzielić realizację od deklaracji. W powyższym przykładzie możesz przenieść ciało metody na zewnątrz klasy. To sprawi, że będzie wyglądał o wiele czystszy i uporządkowany. Jednakże, zgodnie z tym question, powyższy przykład ma domyślne specyfikatory inline. Przeniesienie implementacji z klasy na zewnątrz klasy, jak w poniższym przykładzie, spowoduje błędy linkera, a więc wstawisz wszystko lub przeniesiesz definicje funkcji do pliku .cpp.

Przykład: _ Poniższy przykład spowoduje "błędy linkera definicji wielokrotnej", jeśli nie przeniesiesz definicji funkcji do pliku .cpp lub nie określisz funkcji jako wbudowanej.

// my_class.hpp 
void my_class::my_function() 
{ 
    // ERROR! MULTIPLE DEFINITION OF my_class::my_function 
    // This error only occurs if you #include the file containing this code 
    // in two or more separate source (compiled, .cpp) files. 
} 

Aby rozwiązać ten problem:

//my_class.cpp 
void my_class::my_function() 
{ 
    // Now in a .cpp file, so no multiple definition error 
} 

czyli

// my_class.hpp 
inline void my_class::my_function() 
{ 
    // Specified function as inline, so okay - note: back in header file! 
    // The very first example has an implicit `inline` specifier 
} 

Powód 3chcesz zaoszczędzić miejsce, ponownie, ale tym razem pracujemy z klasa szablonu:

Jeśli pracujemy z klasami szablonów, nie możemy przenieść implementacji do pliku źródłowego (plik .cpp). Obecnie nie jest to dozwolone przez (domyślam się) ani standardowy, ani bieżący kompilator. W przeciwieństwie do pierwszego przykładu z Reason 2, powyżej, możemy umieścić implementację w pliku nagłówkowym.Zgodnie z tym question powodem jest, że metody klasy szablonów mają również domyślne specyfikatory inline. Czy to jest poprawne? (Wydaje się, że to ma sens). Ale nikt nie zdawał sobie sprawy z tego, o czym wspomniałem!

Czy oba poniższe przykłady są identyczne?

// some_header_file.hpp 
#pragma once 

// template class declaration goes here 
class some_class 
{ 
    // Some code 
}; 

// Example 1: NO INLINE SPECIFIER 
template<typename T> 
void some_class::class_method() 
{ 
    // Some code 
} 

// Example 2: INLINE specifier used 
template<typename T> 
inline void some_class::class_method() 
{ 
    // Some code 
} 

Jeśli masz plik nagłówkowy klasy szablon, który staje się ogromne ze względu na wszystkie funkcje tak, to wierzę, że mogą poruszać się definicje funkcji do innego pliku nagłówka (zwykle pliku .tpp?), a następnie #include file.tpp na końcu pliku nagłówkowego zawierającego deklarację klasy. NIE wolno dołączać tego pliku w żadnym innym miejscu, w związku z czym .tpp zamiast .hpp.

Zakładam, że można to również zrobić za pomocą metod liniowych zwykłej klasy? Czy to też jest dozwolone?

Tura pytań

Więc zrobiłem kilka wypowiedzi powyżej, które w większości odnoszą się do struktury plików źródłowych. Myślę, że wszystko, co powiedziałem, było poprawne, ponieważ przeprowadziłem kilka podstawowych badań i "odkryłem pewne rzeczy", ale to jest pytanie, więc nie wiem na pewno.

To wszystko sprowadza się do sposobu porządkowania kodu w plikach. Myślę, że wymyśliłem strukturę, która zawsze będzie działać.

Oto, co wymyśliłem. (Jest to „kod klasy organizacja plik/struktura standardowy Eda Ptasie”, jeśli chcesz Nie wiem, czy to będzie bardzo przydatny jeszcze, że jest sens pytać..)

  • 1: Zadeklaruj klasę (szablon lub inną) w pliku .hpp, w tym wszystkie metody, funkcje przyjaciół i dane.
  • 2: Na dole pliku .hpp, #include plik .tpp zawierający wdrażanie wszelkich inline metod. Utwórz plik .tpp i upewnij się, że wszystkie metody zostały określone jako inline.
  • 3: wszystkich pozostałych członków (funkcje non-inline, funkcje przyjaciel i danych statycznych) powinny być zdefiniowane w pliku .cpp, który #include s plik .hpp na górze, aby uniknąć błędów takich jak „klasa ABC nie została uznana ". Ponieważ wszystko w tym pliku będzie miało zewnętrzne powiązanie, program połączy poprawnie.

Czy standardy takie istnieją w przemyśle? Czy standard wymyślił pracę we wszystkich przypadkach?

+0

Przy okazji przeszedłem twoje pytanie, ponieważ było zbyt długie. Informacje o wbudowanej funkcji szablonu. Słowo kluczowe wbudowane nie jest wymagane (nadal można umieszczać je w plikach nagłówkowych) i nie ma żadnego gwarantowanego efektu. Używanie słowa kluczowego może sprawić, że kompilator będzie bardziej skłonny do wstawienia funkcji, ale nic nie jest pewne. Te funkcje szablonu nie są niejawnie wbudowanymi funkcjami. Odpowiedź na to pytanie jest błędna, co omówiono w odpowiedziach na uwagi. –

+0

@NeilKirk Ah dzięki, pewności wydawało się być dużo niezgody na ten temat. – user3728501

+0

Po prostu zignoruj ​​wstawianie tam, a problem nigdy ci nie przeszkodzi :) –

Odpowiedz

4

Twoje trzy punkty brzmią poprawnie. Jest to standardowy sposób robienia rzeczy (chociaż nie widziałem wcześniej rozszerzenia .tpp, zazwyczaj jest to .inl), chociaż osobiście po prostu umieszczam funkcje inline na dole plików nagłówkowych, a nie w oddzielnym pliku.

Oto jak rozmieszczam moje pliki. Pomijam plik deklaracji forward dla prostych klas.

myclass-fwd.h

#pragma once 

namespace NS 
{ 
class MyClass; 
} 

myclass.h

#pragma once 
#include "headers-needed-by-header" 
#include "myclass-fwd.h" 

namespace NS 
{ 
class MyClass 
{ 
    .. 
}; 
} 

myclass.cpp

#include "headers-needed-by-source" 
#include "myclass.h" 

namespace 
    { 
    void LocalFunc(); 
} 

NS::MyClass::... 

Wymień pragmy ze strażników nagłówka zgodnie z preferencjami ..

Powodem takiego podejścia jest zmniejszenie zależności nagłówka, które spowalniają czasy kompilacji w dużych projektach. Jeśli nie wiesz, możesz przekazać dalej deklarację klasy do użycia jako wskaźnik lub odwołanie. Pełna deklaracja jest potrzebna tylko wtedy, gdy konstruujesz, tworzysz lub używasz członków klasy.

Oznacza to, że inna klasa, która używa klasy (przyjmuje parametry za pomocą wskaźnika/odniesienia), musi jedynie zawierać nagłówek fwd w swoim własnym nagłówku. Pełny nagłówek jest następnie zawarty w pliku źródłowym drugiej klasy. Znacznie zmniejsza to ilość zbędnych śmieci, które dostajesz podczas wciągania dużego nagłówka, który ciągnie kolejny duży nagłówek, który ciągnie następny ...

Następną wskazówką jest nienazwana przestrzeń nazw (nazywana czasem anonimową przestrzenią nazw). To może pojawić się tylko w pliku źródłowym i jest jak ukryta przestrzeń nazw widoczna tylko dla tego pliku. Możesz tutaj umieścić lokalne funkcje, klasy itp., Które są używane tylko przez plik źródłowy. Zapobiega to konfliktom nazw, jeśli utworzysz coś o tej samej nazwie w dwóch różnych plikach. (Na przykład dwie lokalne funkcje F mogą powodować błędy linkera).

+1

Ach tak, dobre wskazówki dotyczące anonimowego obszaru nazw i pliku nagłówkowego deklaracji przekazania. Wiele razy widzę, że szczególnie przydatna byłaby deklaracja terminowa. Dzięki! – user3728501

+0

@NeilKirk: Dlaczego włączasz myclass-fwd.h do myclass.h? Myślałem, że to jest zbyteczne. – quamrana

+0

@quamrana Bez powodu, po to, by być konsekwentnym. –

1

Głównym powodem oddzielenia interfejsu od implementacji jest to, że nie trzeba przekompilowywać całego kodu, gdy coś w jego implementacji ulegnie zmianie; musisz tylko rekompilować zmienione pliki źródłowe.

chodzi o „zadeklarować klasę (szablonu lub w inny sposób)”, następnie template jest nieclass. A template to wzorzec dla klas tworzących. Co ważniejsze, możesz zdefiniować klasę lub szablon w nagłówku. Definicja klasy obejmuje deklaracje jej funkcji składowych, a funkcje składowe niesekwencyjne są zdefiniowane w jednym lub większej liczbie plików źródłowych. Funkcje składowe i wszystkie funkcje szablonu powinny być zdefiniowane w nagłówku, niezależnie od kombinacji bezpośrednich definicji i preferowanych dyrektyw.

+1

"Jeśli chodzi o" Zadeklaruj klasę (szablon lub nie) ", szablon nie jest klasą. Szablon jest wzorcem do tworzenia klas." - tak, wiem – user3728501

0

Czy takie standardy istnieją w przemyśle?

Tak. Z drugiej strony standardy kodowania, które różnią się od tych, które wyraziłeś, można znaleźć również w branży. W końcu mówisz o standardach kodowania, a standardy kodowania są różne: od dobrego do złego, po brzydki.

Czy standard wymyślił pracę we wszystkich przypadkach?

Absolutnie nie. Na przykład,

template <typename T> class Foo { 
public: 
    void some_method (T& arg); 
... 
}; 

Oto definicja klasy szablonu Foo nie wie nic o tym parametrem szablonu T. Co jeśli z jakiegoś szablonu klasy, definicje metod różnić w zależności od parametrów szablonu? Twoja reguła nr 2 po prostu tutaj nie działa.

Inny przykład: Co zrobić, jeśli odpowiadający plik źródłowy jest ogromny, tysiąc linii lub dłuższy? Czasami ma sens dostarczanie implementacji w wielu plikach źródłowych. Niektóre standardy sięgają skrajności dyktowania jednej funkcji w pliku (osobista opinia: Yech!).

Na drugim biegunie pliku źródłowego o długości ponad tysiąca linii znajduje się klasa bez plików źródłowych. Cała implementacja znajduje się w nagłówku. Dużo można powiedzieć o implementacjach tylko nagłówkowych. Jeśli nic innego, to upraszcza, czasem znacznie, problem z łączeniem.

+0

Nagłówek tylko jak w: Wszystkie metody zadeklarowane i zdefiniowane w nawiasach klamrowych ('{' i '}')? Rozumiem twój problem z zapobieganiem błędom linkerów, ale z każdym elementem, który jest bardziej złożony niż jedna linia kodu, na przykład bardziej złożony niż tylko '{return m_time; } ', produkuje ohydny kod. – user3728501

+0

Nagłówek, jak we wszystkich metodach, jest zdefiniowany w pliku nagłówkowym. Niezależnie od tego, czy znajduje się ona w klasie, jako poza linią definicji poza klasą, ale nadal w tym samym pliku nagłówkowym, czy też w innym pliku uwzględnionym w tym pierwszym nagłówku, jest inne pytanie. Kluczowy punkt: Nie ma pliku źródłowego, który musi zostać skompilowany osobno i połączony. Wiele funkcji Boost jest na przykład implementacją tylko nagłówkową. –

+0

O ile coś bardziej złożonego niż '{return something; } 'nie należą do definicji klasy: * To twoja opinia *. Inni ludzie mają inne opinie. Osobiście nie mam nic przeciwko widzeniu krótkiego (ale nie jednego liniowca) w definicji klasy. Moja osobista zasada: jeśli umieściłbym definicję funkcji w klasie, jeśli myślę, że robienie tego pomoże innym zrozumieć tę klasę, to moja opinia. Bądź bardzo ostrożny przy wprowadzaniu osobistych preferencji do standardu kodowania. To sprawi, że ludzie będą ignorować twoje standardy. –

Powiązane problemy