biblioteki i przestrzenie nazw

Download Report

Transcript biblioteki i przestrzenie nazw

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++
prog.o
static library
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++
prog.o
lib.cpp
g++
lib.o
linker
g++
g++
a.out
shared library
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++
lib.o
g++
a.out
g++
loader
dynamic library
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:
namespace przestrzeń
{
// 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ć:
using przestrzeń::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 namespace przestrzeń;
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 static przy
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 nazwami
namespace 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 typedef lub 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 zakresu niż 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).