Aby ułatwić jeszcze bardziej tworzenie w C++ dużych, pisanych przez wielu programistów aplikacji, dodano do języka mechanizm przestrzeni nazw (ang. name space). Jest to narzędzie pozwalające logicznie grupować nazwy obiektów rozmaitego typu: zmiennych, funkcji, klas, wyliczeń, wzorców itd.
Przypuśćmy, że stworzyliśmy w programie zestaw klas i funkcji do obsługi graficznego interfejsu użytkownika (GUI), a ktoś inny napisał zestaw realizujący tzw. business logic. Możemy te dwa zestawy umieścić w osobnych przestrzeniach nazw za pomocą słowa kluczowego namespace, po którym, w nawiasach klamrowych, umieszczamy deklaracje i ewentualnie definicje:
namespace GUI { class Menu { /* ... */ }; void show(Menu&); double calculate(double) { // ... } // ... } namespace Business { class Tax { /* ... */ }; double calculate(double&); // ... }Zauważmy, że w obu przestrzeniach nazw występuje funkcja calculate. W jednej z nich została już zdefiniowana, w drugiej tylko zadeklarowana. Nie powoduje to żadnego konfliktu, bo obie funkcje należą do różnych przestrzeni nazw i nie mają ze sobą nic wspólnego, niezależnie od tego, czy ich sygnatura jest taka sama czy inna. Jeśli umieściliśmy nazwę jakiegoś obiektu (stałej, klasy, funkcji, wyliczenia...) w przestrzeni nazw, to pełną, kwalifikowaną nazwą tego obiektu staje się nazwa obiektu poprzedzona identyfikatorem przestrzeni nazw i czterokropkiem. Na przykład, aby zdefiniować funkcję calculate zadeklarowaną w przestrzeni nazw Business, musimy użyć w definicji pełnej nazwy (chyba, że samą definicję umieścimy wewnątrz bloku namespace):
double Business::calculate(double& z) { // ... }a żeby zdefiniować poza zakresem przestrzeni nazw np. destruktor klasy Menu z przestrzeni nazw GUI:
GUI::Menu::~Menu() { /* ... */ }Do istniejących przestrzeni nazw można dodawać nowe elementy. Na przykład tenże destruktor moglibyśmy zdefiniować tak
namespace GUI { Menu::~Menu() { /* ... */ } const double PI = 3.14; }Zauważmy, że nazwy klasy Menu nie musieliśmy teraz kwalifikować nazwą GUI, bo całą definicję umieściliśmy w zakresie tej przestrzeni poprzez użycie słowa kluczowego namespace. Przy okazji dodaliśmy do tej przestrzeni nazw nowy element w postaci nazwy stałej zmiennopozycyjnej PI.
Funkcje calculate z obu przestrzeni można wywoływać bezkonfliktowo:
double x, y, v, w; // ... x = GUI::calculate(v); y = Business::calculate(w);W ten sposób nazwy z różnych przestrzeni nazw są od siebie odseparowane. Jeśli na przykład różne fragmenty aplikacji lub bibliotek piszą różni autorzy, to każdy może stworzyć dla swoich klas czy funkcji oddzielną przestrzeń nazw i nie przejmować się ewentualnym konfliktem nazw; użytkownik jego klasy będzie musiał świadomie ją wybrać kwalifikując nazwę klasy nazwą przestrzeni nazw.
Z drugiej strony, jeśli jesteśmy pewni, że żadnego konfliktu nie będzie, to konieczność poprzedzania wszystkich identyfikatorów nazwą przestrzeni może być nieco uciążliwa. Można temu zaradzić poprzez deklarację użycia. Deklaracja taka, wyrażona za pomocą słowa kluczowego using, informuje kompilator, że pewna nazwa oznaczać będzie obiekt nią identyfikowany z pewnej konkretnej przestrzeni nazw, stając się jej synonimem (aliasem). Na przykład po
using GUI::calculate;w zasięgu, w którym deklaracja ta została użyta, niekwalifikowana nazwa calculate będzie się odnosić do takiej nazwy z przestrzeni GUI, a zatem będzie synonimem nazwy GUI::calculate. Oczywiście do calculate z przestrzeni Business w dalszym ciągu możemy się odnosić, ale tę nazwę będziemy musieli kwalifikować, czyli pisać jawnie Business::calculate(x).
Można w końcu, co nie jest na ogół zalecane, włączyć do aktualnego zakresu wszystkie nazwy z pewnej przestrzeni nazw. Służy do tego dyrektywa użycia, wyrażona przez dwa słowa kluczowe using namespace. Na przykład po
using namespace GUI;w aktualnym zasięgu można używać wszystkich nazw z przestrzeni GUI bez kwalifikowania ich nazwą tej przestrzeni.
Jeśli przestrzeń nazw, którą za pomocą dyrektywy użycia „otwieramy” jest duża i nam niezbyt dobrze znana, to taką dyrektywą można doprowadzić do konfliktów nazw, uniknięciu których przestrzenie nazw miały przecież służyć.
Rozpatrzmy jeszcze prosty przykład:
1. #include <iostream> 2. 3. namespace A { 4. const int two = 2; 5. const int six = 6; 6. void write() { std::cout << "ns-A" << " "; } 7. } 8. 9. namespace B { 10. void write() { std::cout << "ns-B" << " "; } 11. } 12. 13. namespace C { 14. const int two = 22; 15. const int six = 66; 16. } 17. 18. int main() { 19. using A::write; 20. using namespace C; 21. 22. write(); 23. B::write(); 24. std::cout << six << std::endl; 25. }
Zauważmy, że nazwy cout i endl w tym programie kwalifikujemy nazwą przestrzeni nazw std (linie 6, 10 i 24). Włączyliśmy bowiem nagłówek iostream, ale nie napisaliśmy zaraz potem sakramentalnego
using namespace std;Otóż wszystkie udogodnienia biblioteki standardowej, do których mamy dostęp po włączeniu plików nagłówkowych (między innymi pliku iostream), są umieszczane w przestrzeni nazw std. Wyjątkiem są funkcje operator new i operator delete oraz makra preprocesora. Normalnie, dla uproszczenia, w naszych przykładowych programach otwieraliśmy na samym początku całą tę przestrzeń nazw, właśnie za pomocą powyższej dyrektywy użycia. Dzięki temu nie musieliśmy kwalifikować jej nazwą takich nazw, jak cout czy endl. Teraz natomiast tej przestrzeni nie otworzyliśmy, więc nazwy z niej pochodzące musieliśmy kwalifikować.
Przestrzenie nazw to stosunkowo nowy element języka C++. W C tradycyjnie używa się również plików nagłówkowych, ale wszystkie nazwy w nich deklarowane są jednakowo dostępne bez żadnych kwalifikacji, bo nie ma tam mechanizmu przestrzeni nazw. Trzeba było zatem zapewnić możliwość kompilowania przez kompilatory C++ programów napisanych w C. Osiągnięto to poprzez umowę, ż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 domyślnej 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 o tradycyjnej nazwie nazwa.h są w C++ nazywane cnazwa. Tak więc dyrektywa preprocesora
#include <string.h>jest równoważna
#include <cstring>tyle że w pierwszym przypadku zawartość jest włączana do domyślnej przestrzeni nazw, zaś w drugim do przestrzeni std. To samo dotyczy następujących 18 plików nagłówkowych pochodzących z C:
Plik iostream.h jest bardzo często używany zamiast iostream. Zauważmy, że nie pochodzi on w ogóle z C, więc powyżej opisana zasada go nie dotyczy. Nie powinno się go nigdy używać, gdyż nie należy do standardu i w związku z tym może w ogóle nie istnieć! Zawsze należy stosować prawidłową nazwę iostream.
Pozostałe 32 standardowe pliki nagłówkowe nie pochodzą z C i są charakterystyczne tylko dla C++:
Zakończmy ten rozdział inną wersją przykładu
stack.cpp.
Teraz nazwy z modułu opisującego stos umieszczamy w przestrzeni nazw
mySTACKS, co zapobiega konfliktom między nazwami
zadeklarowanymi w tym module a nazwami pochodzącymi z innych
przestrzeni nazw. Nagłówek deklarujący abstrakcyjną klasę
STACK
umieszczamy w pliku
mySTACK.h
1. #ifndef mySTACK_H 2. #define mySTACK_H 3. 4. namespace mySTACKS { 5. class STACK 6. { 7. public: 8. virtual void push(int) = 0; 9. virtual int pop() = 0; 10. virtual bool empty() = 0; 11. static STACK* getInstance(int); 12. virtual ~STACK() { } 13. }; 14. } 15. #endif
1. #ifndef mySTACKS_H 2. #define mySTACKS_H 3. 4. #include "mySTACK.h" 5. 6. namespace mySTACKS { 7. 8. class ListStack: public STACK { 9. struct Node { 10. int data; 11. Node* next; 12. Node(int data, Node* next); 13. }; 14. Node* head; 15. ListStack(); 16. public: 17. friend STACK* STACK::getInstance(int); 18. int pop(); 19. void push(int data); 20. bool empty(); 21. ~ListStack(); 22. }; 23. 24. class ArrayStack : public STACK { 25. int top; 26. int* arr; 27. ArrayStack(); 28. public: 29. friend STACK* STACK::getInstance(int); 30. void push(int data); 31. int pop(); 32. bool empty(); 33. ~ArrayStack(); 34. }; 35. } 36. #endif
1. #include <iostream> 2. #include "mySTACKS.h" 3. using namespace mySTACKS; 4. 5. // ListStack 6. 7. ListStack::Node::Node(int data, Node* next) 8. : data(data), next(next) 9. { } 10. 11. ListStack::ListStack() { 12. head = NULL; 13. std::cerr << "Creating ListStack" << std::endl; 14. } 15. 16. int ListStack::pop() { 17. int data = head->data; 18. Node* temp = head->next; 19. delete head; 20. head = temp; 21. return data; 22. } 23. 24. void ListStack::push(int data) { 25. head = new Node(data, head); 26. } 27. 28. bool ListStack::empty() { 29. return head == NULL; 30. } 31. 32. ListStack::~ListStack() { 33. std::cerr << "Deleting ListStack" << std::endl; 34. while (head) { 35. Node* node = head; 36. head = head->next; 37. std::cerr << " node " << node->data << std::endl; 38. delete node; 39. } 40. } 41. 42. // ArrayStack 43. 44. ArrayStack::ArrayStack() { 45. top = 0; 46. arr = new int[100]; 47. std::cerr << "Creating ArrayStack" << std::endl; 48. } 49. 50. void ArrayStack::push(int data) { 51. arr[top++] = data; 52. } 53. 54. int ArrayStack::pop() { 55. return arr[--top]; 56. } 57. 58. bool ArrayStack::empty() { 59. return top == 0; 60. } 61. 62. ArrayStack::~ArrayStack() { 63. std::cerr << "Deleting ArrayStack with " << top 64. << " elements remaining" << std::endl; 65. delete [] arr; 66. } 67. 68. // STACK 69. 70. STACK* STACK::getInstance(int size) { 71. if (size > 100) 72. return new ListStack(); 73. else 74. return new ArrayStack(); 75. }
cpp> g++ -pedantic-errors -Wall -c mySTACKSImpl.cppTylko plik mySTACKSImpl.o i plik nagłówkowy mySTACK.h jest teraz potrzebny, aby skompilować aplikację wykorzystującą nasz moduł:
1. #include <iostream> 2. #include "mySTACK.h" 3. 4. int main() { 5. 6. mySTACKS::STACK* stack; 7. 8. stack = mySTACKS::STACK::getInstance(120); 9. stack->push(1); 10. stack->push(2); 11. stack->push(3); 12. stack->push(4); 13. std::cout << stack->pop() << " "; 14. std::cout << stack->pop() << std::endl; 15. delete stack; 16. 17. stack = mySTACKS::STACK::getInstance(50); 18. stack->push(1); 19. stack->push(2); 20. stack->push(3); 21. stack->push(4); 22. std::cout << stack->pop() << " "; 23. std::cout << stack->pop() << std::endl; 24. delete stack; 25. }
cpp> g++ -pedantic-errors -Wall -o stacksApp \ stacksApp.cpp mySTACKSImpl.otworzy plik wynikowy stacksApp, który po uruchomieniu daje
cpp> ./stacksApp Creating ListStack 4 3 Deleting ListStack node 2 node 1 Creating ArrayStack 4 3 Deleting ArrayStack with 2 elements remaining
T.R. Werner, 21 lutego 2016; 20:17