27 sty 2010

Deklaracje zapowiadające w celu przyspieszenia kompilacji projektu

W projektach pisanych w C++ lubię korzystać z biblioteki boost.
Niestety ma ona jedną uciążliwą wadę: kod wykorzystujący boosta strasznie wolno się kompiluje. Przyczyną tego jest fakt, iż boost w dużym stopniu wykorzystuje template-y i to w bardzo zaawansowany sposób. Metaprogramy to dla kompilatora c++ nie lada wyzwanie!

W projektach składających się z kilkudziesięciu plikow źródłowych czas kompilacji całości przekracza kilka minut! Nawet przy kompilacji przyrostowej z użyciem make, po zmianie jednego z headerów w projekcie kompilacja może trwać powyżej minuty!

Jedną z technik przyspieszających kompilację jest unikanie dodawania #include do własnych headerów. Jeżeli tylko można należy wstępnie zadeklarować klasę w headerze, a właściwy #include dodać dopiero do pliku cpp.

Poniżej przykład:
Piszemy klasę Database, w której chcemy skorzystać z klasy XmlDocument. Ściślej: obiekty klasy XmlDocument będą przekazywane jako parametr w metodach klasy Database.

Jak zatem powinniśmy napisać header klasy Database.hpp:
class XmlDocument;

class Database
{
    void init( XmlDocument &document );
}

A co jeśli do obiektu XmlDocument chcemy mieć dostęp nie przez referencję, ale przez smart pointer (boost::shared_ptr)?

Żeby zdefiniować smart pointer wcale nie jest potrzebny header klasy. Także tutaj wystarczy jej deklaracja zapowiadająca:
class XmlDocument;
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

class Database
{
    void init( XmlDocumentPtr document );
}


Dodatkowa wskazówka: aby uniknąć duplikowania kodu, dla klas z których chcę korzystać poprzez smart pointer, tworzę osobny - dodatowy header KlasaXXXPtr.hpp

Dla przykładu header taki dla klasy XmlDocument będzie wyglądał tak:
#ifndef XMLDOCUMENTPTR_HPP_
#define XMLDOCUMENTPTR_HPP_

#ifndef XMLDOCUMENT_H_
class XmlDocument;
#endif // XMLDOCUMENT_H_

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

#endif /* XMLDOCUMENTPTR_HPP_ */
Wszędzie, gdzie chcę zadeklarować smart pointera na XmlDocument po prostu wpisuję #include "XmlDocumentPtr.hpp", natomiast w pliku cpp (np. Database.cpp) muszę jeszcze dodać #include "XmlDocument.hpp".

4 komentarze:

  1. Zastanawiam się nad deklaracją zapowiadającą do klasy wewnętrznej innej klasy.


    class A
    {
    class a
    {
    };
    };

    class B
    {
    A::a* wskaznik;
    };


    Deklaracja zapowiadająca

    class A::a;

    jest niepoprawna. Jak należy to zapisać? A co jeśli struktura klas wewnętrznych będzie jeszcze głębsza?


    class A
    {
    class a1
    {
    class a2
    {
    class a3
    {
    };
    };
    };
    };

    class B
    {
    A::a3* wskaznik;
    };


    Problem wydaje się być znacznie trudniejszy :)

    Pozdrawiam
    Łukasz Gotszald

    OdpowiedzUsuń
  2. Nie udało mi się zmusić kompilator do zadeklarowania klasy wewnętrznej.

    Pytanie po co nam klasa wewnętrzna?
    Wewnętrzna zazwyczaj mówi: internal (nieużywana poza klasą zewnętrzną). Wobec czego potrzeba jej zapowiedzi jest mniejsza.

    Czasem wewnętrzna klasa to jakiś helper (klasa pomocnicza), używana poza klasą zewnętrzną.

    W takim przypadku przeniósłbym ją poza klasę zewnętrzną. Aby podkreślić związek tych dwóch klas, można wrzucić je do jednego namespace.
    Często tak robię: pliki realizujące tę samą funkcjonalność wrzucam w projekcie do jednego folderu, a typy z tych plików wrzucam do wspólnego namespace.

    Np.

    namespace A_implementation
    {
    class A;
    class AA;
    }

    OdpowiedzUsuń
  3. To chyba nie jest dobre podejście do problemu: "jeśli nie wiem jak to zrobić to zapytam - po co?". Wtedy ktoś inny mógłby zapytać "po co nam programowanie?".

    Ja znalazłem zastosowanie dla tej techniki, to jest dla mnie ważne i niestety nie można tego obejść bez utraty szybkości algorytmu. Przestrzenie nazw to zupełnie inna sprawa. Ja opisałem problem zapowiadania wskaźnika do klasy wewnętrznej innej klasy a nie ogólny problem tworzenia klas wewnętrznych.

    Pozdrawiam
    Łukasz Gotszald

    OdpowiedzUsuń
  4. W żadnym razie nie chciałem podważyć zasadności używania klas wewnętrznych.

    Chodziło mi raczej o to, iż napotkaliśmy problem: chcemy zadeklarować wskaźnik do klasy wewnętrznej, szukamy, szukamy i... nie znajdujemy rozwiązania. Może nasz kompilator na to nie pozwala?
    Pytanie: szukać dalej? A może nie iść "w maliny" i inaczej rozwiązać problem?

    W tym przypadku widzę następujące rozwiązania:
    a) rezygnujemy z deklaracji zapowiadającej dla wewnętrznej klasy
    b) albo przenosimy klasę wewnętrzną na zewnątrz i pakujemy ją do namespace

    Pytanie po co nam klasa wewnętrzna miało pomóc w wyborze kompromisu.

    Przyznam się, że w większości przypadków gdy deklaruję klasę wewnętrzną w C++, po pewnym czasie i tak ląduje ona na zewnątrz.
    I nie dlatego że potrzebuję ją wstępnie zadeklarować, ale dlatego, że jej kod tak się rozrósł iż staje się nieczytelny: plik .hpp zawiera za dużo kodu, a plik .cpp zawiera długie deklaracje w stylu:
    void OuterClass::InnerClass::Function()
    {
    }

    Oczywiście każdy ma swój własny styl. Ważne żeby się go trzymać :-)

    Pozdrawiam Panie Łukaszu

    OdpowiedzUsuń