300 likes | 418 Views
C++ wykład 9 (11.04.2012). Biblioteki i przestrzenie nazw. Moduły. Każdy większy program składa się z pewnej liczby oddzielnych części – modułów. Moduł to kompletny fragment programu (moduł obliczeniowy, moduł we/wy, moduł prezentacji, itp). Podział kodu na moduły porządkuje logikę programu.
E N D
C++wykład 9 (11.04.2012) Biblioteki i przestrzenie nazw
Moduły • Każdy większy program składa się z pewnej liczby oddzielnych części – modułów. • Moduł to kompletny fragment programu (moduł obliczeniowy, moduł we/wy, moduł prezentacji, itp). • Podział kodu na moduły porządkuje logikę programu. • Należy minimalizować zależności między modułami.
Biblioteki • Moduły, z których może korzystać wiele programów umieszcza się w oddzielnych skompilowanych plikach, zwanych bibliotekami. • Typy bibliotek w C++: • biblioteka statyczna jest dołączana do programu wynikowego na etapie linkowania; • biblioteka współdzielona jest dołączana do programu w trakcie ładowania programu do pamięci; • biblioteka dynamiczna jest dołączana do uruchomionego procesu w trakcie działania programu.
Biblioteki • Biblioteka to zbiór klas, funkcji i zmiennych, z których mogą korzystać różne programy. • Biblioteka ma postać binarną – jej poszczególne fragmenty są skompilowane (biblioteka jest kolekcją plików obiektowych). • Korzystanie z bibliotek ułatwia programowanie (korzystamy z gotowych i sprawdzonych fragmentów kodu) i przyspiesza proces rekompilacji.
Biblioteki • Wynikiem samej kompilacji pliku źródłowego (plik.c albo plik.cpp) jest plik plik.o pod Linuxem albo plik.obj pod Windowsem. • Biblioteki statyczne mają nazwy libmodul.a pod Linuxem albo modul.lib pod Windowsem. • Biblioteki dynamiczne mają nazwy libmodul.so pod Linuxem (tak jak biblioteki współdzielone) albo modul.dll pod Windowsem.
Biblioteka statyczna • Używając biblioteki statycznej przekazujemy jej archiwum linkerowi w czasie kompilacji. Linker wyszukuje w nim tylko tych plików obiektowych, które są niezbędne do działania naszego programu i kopiuje je bezpośrednio do programu. • Program wynikowy korzystający z biblioteki statycznej jest obszerniejszy ale szybciej się ładuje do pamięci. • Program wynikowy zlinkowany z biblioteką statyczną jest niezależny od plików zewnętrznych. • Uaktualnienie biblioteki wymaga rekompilacji programu.
Biblioteka statyczna lib.cpp g++ lib.o prog.cpp ar g++ static library prog.o linker g++ a.out loader ssh memory
Biblioteka współdzielona • Programy korzystające biblioteki współdzielonej nie zawierają bezpośrednio kodu z tej biblioteki a tylko referencję do niej. • Program wynikowy korzystający z biblioteki współdzielonej jest chudszy ale wolniej ładuje się do pamięci (biblioteki współdzielone są odszukiwane i ładowane do pamięci razem z programem). • Program wynikowy skompilowany z biblioteką współdzieloną jest zależny od plików zewnętrznych. • Zmodyfikowanie biblioteki współdzielonej spowoduje zmianę w działaniu programu ale bez jego ponownej kompilacji.
Biblioteka współdzielona prog.cpp g++ lib.cpp prog.o g++ lib.o linker g++ g++ shared library a.out loader ssh memory
Biblioteka dynamiczna • Programy korzystające bibliotek dynamicznych nie zawierają bezpośrednio kodu z tej biblioteki ale muszą korzystać ze specjalnych metod włączania takich bibliotek w trakcie działania programu (plik nagłówkowy <dlfcn.h>). • Program wynikowy korzystający z biblioteki dynamicznej jest chudszy i szybciej ładuje się do pamięci, ale działa wolniej (biblioteki dynamiczne można załadować w dowolnym momencie w trakcie działania programu). • Program wynikowy skompilowany z biblioteką dynamiczną jest zależny od plików zewnętrznych. • Zmodyfikowanie biblioteki dynamicznej spowoduje zmianę w działaniu programu ale bez jego ponownej kompilacji.
Biblioteka dynamiczna prog.cpp g++ prog.o lib.cpp linker g++ g++ lib.o a.out g++ dynamic library loader a.out ssh memory
Tworzenie bibliotek (pod Linuxem) Tworzenie programu bez dołączanych bibliotek. • Załóżmy, że mamy pliki src1.cpp, src2.cpp i src3.cpp, które stanowią moduł obliczeniowy oraz plik prog.cpp, który będzie korzystał z funkcji i klas zdefiniowanych w module obliczeniowym. • Aby skompilować cały program razem z modułem obliczeniowym należy wydać polecenie:$ g++ -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp prog.cpp -o calculation • Aby skompilować cały program razem z modułem obliczeniowym i statycznie zlinkować z innymi bibliotekami (rozmiar programu wynikowego będzie znacznie większy) należy wydać polecenie:$ g++ -static … • Aby uruchomić skompilowany program należy wydać polecenie:$ ./calculation • Aby sprawdzić z jakimi bibliotekami jest linkowany program i jakie symbole są w nim użyte należy wydać polecenie:$ ldd calculation$ nm calculation
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki statycznej. • Najpierw kompilujemy pliki źródłowe do plików obiektowych:$ g++ -c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp • Następnie łączymy pliki obiektowe do jednego pliku bibliotecznego libsrc.a:$ ar crs libsrc.a src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i zlinkować go z biblioteką:$ g++ -c -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –L. –lsrc • Teraz można uruchomić skompilowany program:$ ./calculation • Wyjaśnienie: • opcja -Lścieżka określa ścieżkę do biblioteki, • opcja -lbiblioteka określa nazwę biblioteki.
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki współdzielonej. • Najpierw kompilujemy pliki źródłowe z opcją -fpic do plików obiektowych:$ g++ -fpic –c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp • Następnie łączymy pliki obiektowe do jednego pliku bibliotecznego libsrc.so:$ g++ –fpic -shared -o libsrc.so src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i wlinkować do niego informacje o bibliotece:$ g++ -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –L. –lsrc • Przed uruchomieniem programu trzeba zapisać w zmiennej środowiskowej LD_LIBRARY_PATH ścieżkę do biblioteki:$ export LD_LIBRARY_PATH="LD_LIBRARY_PATH:ścieżka" • Teraz można uruchomić skompilowany program:$ ./calculation
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki dynamicznej. • Bibliotekę dynamiczną przygotowujemy tak samo jak bibliotekę współdzieloną libsrc.so:$ g++ -fpic –c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp $ g++ –fpic -shared -o libsrc.so src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i dołączyć do niego dynamicznego linkera opcją -ldl:$ g++ -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –ldl • Teraz można uruchomić skompilowany program:$ ./calculation • Wyjaśnienie: • aby skorzystać z dynamicznego linkera należy do programu włączyć plik nagłówkowy <dlfcn.h>; • aby załadować dynamiczną bibliotekę trzeba skorzystać z funkcji dlopen, dlsym, dlerror i dlclose.
Przestrzenie nazw • Przestrzeń nazw to obszar, w którym umieszcza się różne deklaracje i definicje. • Przestrzeń nazw definiuje zasięg, w którym dane nazwy będą obowiązywać i będą dostępne. • Przestrzenie nazw rozwiązują problem kolizji nazw. • Przestrzenie nazw wspierają modularność kodu.
Definicja przestrzeni nazw • Przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace, ograniczając zawartość klamrami:namespaceprzestrzeń{ // deklaracje i definicje} • Aby odnieść się do typu, funkcji albo obiektu umieszczonego w przestrzeni nazw musimy stosować kwalifikator zakresu przestrzeń:: poza tą przestrzenią. • Funkcja main() musi być globalna, aby środowisko uruchomieniowe rozpoznało ją jako funkcję specjalną. • Do nazw globalnych odnosimy się za pomocą pustego kwalifikatora zakresu ::, na przykład ::wspolczynnik. • Jeśli w przestrzeni nazw zdefiniujemy klasę to do składowej statycznej w takiej klasie odnosimy się kwalifikując najpierw nazwą przestrzeni a potem nazwą klasy przestrzeń::klasa::składowa.
Definicja przestrzeni nazw • Przykład przestrzeni nazw:namespace wybory{ int min2 (int, int); int min3 (int, int, int);}int wybory::min2 (int a, int b) { return a<b ? a : b; }int wybory::min3 (int a , int b , int c) { return min2(min2(a,b),c); }int min4 (int a, int, b, int c, int d){ return wybory::min2( wybory::min2(a,b), wybory::min2(c,d));}
Deklaracja użycia • Deklaracja użycia wprowadza lokalny synonim nazwy z innej przestrzeni nazw (wskazanej nazwy można wówczas używać bez kwalifikowania jej nazwą przestrzeni). • Deklaracja użycia using ma postać:usingprzestrzeń::symbol; • Deklaracja użycia obowiązuje do końca bloku, w którym wystąpiła. • Deklaracje użycia stosujemy w celu poprawienia czytelności kodu. • Deklaracje użycia należy stosować tak lokalnie, jak to jest możliwe. • Jeśli większość funkcji w danej przestrzeni nazw korzysta z jakiejś nazwy z innej przestrzeni, to deklaracje użycia można włączyć do przestrzeni nazw.
Dyrektywa użycia • Dyrektywa użycia udostępnia wszystkie nazwy z określonej przestrzeni nazw. • Dyrektywa użycia using namespace ma postać:using namespaceprzestrzeń; • Dyrektywy użycia stosuje się najczęściej w funkcjach, w których korzysta się z wielu symboli z innej niż ta funkcja przestrzeni nazw. • Globalne dyrektywy użycia są stosowane do transformacji kodu i nie powinno się ich stosować do innych celów. • Globalne dyrektywy użycia w pojedynczych jednostkach translacji (w plikach .cpp) są dopuszczalne w programach testowych czy w przykładach, ale w produkcyjnym kodzie jest to niestosowne i jest uważane za błąd. • Globalnych dyrektyw użycia nie wolno stosować w plikach nagłówkowych!
Anonimowe przestrzenie nazw • Anonimową przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace bez nazwy, ograniczając zawartość klamrami:namespace{ // deklaracje i definicje} • Anonimowa przestrzeń nazw zastępuje użycie deklaratora staticprzy nazwie globalnej – dostęp do nazw zdefiniowanych w przestrzeni anonimowej jest ograniczony do bieżącego pliku. • Dostęp do anonimowej przestrzeni nazw jest możliwy dzięki niejawnej dyrektywie użycia.namespace $$${ // deklaracje i definicje}using namespace $$$;W anonimowej przestrzeni nazw $$$ jest unikatową nazwą w zasięgu, w którym jest zdefiniowana ta przestrzeń.
Przeszukiwanie nazw • Gdy definiujemy funkcję z jakiejś przestrzeni nazw (przed nazwą definiowanej właśnie funkcji stoi kwalifikator przestrzeni) to w jej wnętrzu dostępne są wszystkie nazwy z tej przestrzeni. • Funkcja z argumentem typu T jest najczęściej zdefiniowana w tej samej przestrzeni nazw co T. Jeżeli więc nie można znaleźć funkcji w kontekście, w którym się jej używa, to szuka się jej w przestrzeniach nazw jej argumentów. • Jeżeli funkcję wywołuje metoda klasy K, to pierwszeństwo przed funkcjami znalezionymi przez typy argumentów mają metody z klasy K i jej klas bazowych.
Aliasy przestrzeni nazw • Jeżeli użytkownicy nadają przestrzeniom nazw krótkie nazwy, to mogą one spowodować konflikt. Długie nazwy są niewygodne w użyciu. Dylemat ten można rozwiązać za pomocą krótkiego aliasu dla długiej nazwy przestrzeni nazw. • Aliasy dla przestrzeni nazw tworzymy za pomocą słowa kluczowego namespace z dwiema nazwaminamespace krótka = długa_nazwa_przestrzeni; • Przykład:namespace American_Telephone_and_Telegraph{ // tutaj zdefiniowano Napis}namespace ATT = American_Telephone_and_Telegraph;American_Telephone_and_Telegraph::Napis n = "x";AAT::Napis nn = "y"; • Nadużywanie aliasów może prowadzić do nieporozumień!
Komponowanie i wybór • Interfejsy projektuje się po to, by zminimalizować zależności pomiędzy różnymi częściami programu. Minimalne interfejsy prowadzą do systemów łatwiejszych do zrozumienia, w których lepiej ukrywa się dane i implementację, łatwiej się je modyfikuje oraz szybciej kompiluje. • Eleganckim narzędziem do konstruowania interfejsów są przestrzenie nazw.
Komponowanie i wybór • Gdy chcemy utworzyć interfejs z istniejących już interfejsów to stosujemy komponowanie przestrzeni nazw za pomocą dyrektyw użycia, na przykład:namespace His_string { class String { /* ... */ }; String operator+ (const String&, const String&); String operator+ (const String&, const char*); void fill (char) ; // ... }namespace Her_vector { template<class T> class Vector { /* ... */ }; // ... }namespace My_lib {using namespace His_string;using namespace Her_vector; void my_fct(String&) ; } • Dyrektywa użycia wprowadza do zasięgu wszystkie deklarację z podanej przestrzeni nazw.
Komponowanie i wybór • Teraz przy pisaniu programu można posługiwać się My_lib:void f () { My_lib::String s = "Byron"; // znajduje My_lib::His_string::String // ...}using namespace My_lib;void g (Vector<String> &vs) { // ... my_fct(vs[5]); // ...}
Komponowanie i wybór • Gdy chcemy utworzyć interfejs i dołożyć do niego kilka nazw z innych interfejsów to stosujemy wybór za pomocą deklaracji użycia, na przykład:namespace My_string {using His_string::String;using His_string::operator+; // …} • Deklaracja użycia wprowadza do zasięgu każdą deklarację o podanej nazwie. Pojedyncza deklaracja użycia może wprowadzić każdy wariant funkcji przeciążonej.
Komponowanie i wybór • Łączenie komponowania (za pomocą dyrektyw użycia) z wyborem (za pomocą deklaracji użycia) zapewnia elastyczność potrzebną w praktyce. Z użyciem tych mechanizmów możemy zapewnić dostęp do wielu udogodnień, a zarazem rozwiązać problem konfliktu nazw i niejednoznaczności wynikających z komponowania. • Nazwy zadeklarowane jawnie w przestrzeni nazw (łącznie z nazwami wprowadzonymi za pomocą deklaracji użycia) mają pierwszeństwo przed nazwami wprowadzonymi za pomocą dyrektyw użycia. • Nazwę w nowej przestrzeni nazw można zmienić za pomocą instrukcji typedeflub poprzez dziedziczenie.
Przestrzenie nazw są otwarte • Przestrzeń nazw jest otwarta, co oznacza, że można do niej dodawać nowe pojęcia w kilku deklaracjach (być może rozmieszczonych w różnych plikach), na przykład:namespace NS { int f (); // NS ma nową składową f()}namespace NS { int g (); // teraz NS ma dwie składowe f() i g()} • Definiując wcześniej zadeklarowaną składową w przestrzeni nazw, bezpieczniej jest użyć operatora zakresuniż ponownie otwierać przestrzeń (kompilator nie wykryje literówek w nazwie składowej), na przykład:namespace NS { int h (); }int NS::hhh () // błąd - zamiast h napisano hhh { /*…*/ }
Przestrzeń nazw std • W języku C++ wszystkie nazwy z biblioteki standardowej są umieszczone w przestrzeni nazw std. • W języku C tradycyjnie używa się plików nagłówkowych i wszystkie nazwy w nich deklarowane są w przestrzeni globalnej (dostępne bez żadnych kwalifikacji). • Aby zapewnić możliwość kompilowania przez kompilatory C++ programów napisanych w C przyjęto, że jeśli użyjemy tradycyjnej (pochodzącej z C) nazwy pliku nagłówkowego, to odpowiedni plik jest włączany i zadeklarowane w nim nazwy są dodawane do globalnej przestrzeni nazw. Jeśli natomiast ten sam plik nagłówkowy włączymy pod nową nazwą, to nazwy w nim deklarowane są dodawane do przestrzeni nazw std. Przyjęto przy tym konwencję, że pliki nagłówkowe z C nazwie nazwa.h są w C++ nazywane cnazwa (pary plików <math.h> i <cmath>, itp).