Biblioteka standardowa języka C++ udostępnia bardzo przydatne kontenery (np. unordered_map
), których nie ma w bibliotece C. 😓
Często potrzebujemy łączyć kod C++ z kodem spadkowym w C. Celem tego zadania jest napisanie w C++ dwóch modułów obsługujących słowniki przechowujące klucze wraz z wartościami w postaci ciągów znaków, tak aby można ich było używać w C.
Każdy moduł składa się z pliku nagłówkowego (z rozszerzeniem h) i pliku z implementacją (z rozszerzeniem cc).
Moduł dict (pliki dict.h
i dict.cc
) powinien udostępniać następujące funkcje:
-
unsigned long dict_new();
Tworzy nowy, pusty słownik i zwraca jego identyfikator. -
void dict_delete(unsigned long id);
Jeżeli istnieje słownik o identyfikatorzeid
, usuwa go, a w przeciwnym przypadku nic nie robi. -
size_t dict_size(unsigned long id);
Jeżeli istnieje słownik o identyfikatorzeid
, zwraca liczbę jej elementów, a w przeciwnym przypadku zwraca0
. -
void dict_insert(unsigned long id, const char* key, const char* value);
Jeżeli istnieje słownik o identyfikatorzeid
orazkey != NULL
ivalue != NULL
, wstawia wartość value pod kluczemkey
. W przeciwnym przypadku nic nie robi. Gwarancje odnośnie kosztów wstawienia mają być takie same jak w przypadku konteneraunordered_map
(plus koszt odnalezienia słownika o danym identyfikatorze). -
void dict_remove(unsigned long id, const char* key);
Jeżeli istnieje słownik o identyfikatorzeid
i zawiera kluczkey
, to usuwa klucz oraz wartość związaną z tym kluczem, a w przeciwnym przypadku nic nie robi. -
const char* dict_find(unsigned long id, const char* key);
Jeżeli istnieje słownik o identyfikatorzeid
i zawiera wartość pod kluczemkey
, to zwraca wskaźnik do tej wartości, a w przeciwnym zwraca wartość ze słownika globalnego. Jeśli w słowniku globalnym nie ma wartości pod kluczemkey
, to zwracaNULL
. -
void dict_clear(unsigned long id);
Jeżeli istnieje słownik o identyfikatorzeid
, usuwa wszystkie jego elementy, a w przeciwnym przypadki nic nie robi. -
void dict_copy(unsigned long src_id, unsigned long dst_id);
Jeżeli istnieją słowniki o identyfikatorachsrc_id
orazdst_id
, to kopiuje zawartość słownika o identyfikatorzesrc_id
do słownika o identyfikatorzedst_id
, a w przeciwnym przypadku nic nie robi.
Moduł dictglobal (pliki dictglobal.h
i dictglobal.cc
) powinien udostępniać
funkcję
-
unsigned long dict_global();
Zwraca identyfikator globalnego słownika, którego nie można usunąć.
oraz stałą
-
MAX_GLOBAL_DICT_SIZE
Stała jest równa42
i określa maksymalny rozmiar globalnego słownika. Do globalnego słownika można wstawiać kolejne klucze z wartościami tylko, gdy jego rozmiar po wstawieniu nie będzie przekraczał maksymalnego rozmiaru.
Moduły dict i dictglobal powinny sprawdzać poprawność parametrów i poprawność wykonania funkcji za pomocą asercji i wypisywać na standardowy strumień błędów informacje diagnostyczne. Kompilowanie z parametrem -DNDEBUG
powinno wyłączać wypisywanie.
Przykład użycia znajduje się w pliku dict_test1.c
. Przykład informacji wypisywanych przez dict_test1.c
znajduje się w pliku dict_test1.err
. Aby umożliwić używanie modułów dict oraz dictglobal w języku C++, przygotuj pliki nagłówkowe cdict
oraz cdictglobal
, które umieszczają interfejsy modułów dict i dictglobal w przestrzeni nazw jnp1. Przykłady użycia znajdują się w plikach dict_test2a.cc
i dict_test2b.cc
.
g++ -c -Wall -Wextra -O2 -std=c++17 dict.cc -o dict.o
g++ -c -Wall -Wextra -O2 -std=c++17 dictglobal.cc -o dictglobal.o
gcc -c -Wall -Wextra -O2 dict_test1.c -o dict_test1.o
g++ -c -Wall -Wextra -O2 -std=c++17 dict_test2a.cc -o dict_test2a.o
g++ -c -Wall -Wextra -O2 -std=c++17 dict_test2b.cc -o dict_test2b.o
g++ dict_test1.o dict.o dictglobal.o -o dict_test1
g++ dict_test2a.o dict.o dictglobal.o -o dict_test2a
g++ dict_test2b.o dict.o dictglobal.o -o dict_test2b
Oczekiwane rozwiązanie powinno korzystać z kontenerów i metod udostępnianych przez standardową bibliotekę C++. Nie należy definiować własnych struktur lub klas. Do implementacji słowników należy użyć standardowego typu std::unordered_map<std::string, std::string>
. W szczególności nie należy przechowywać przekazanych przez użytkownika wskaźników const char*
bezpośrednio, bowiem użytkownik może po wykonaniu operacji modyfikować dane pod uprzednio
przekazanym wskaźnikiem lub zwolnić pamięć. Na przykład poniższy kod nie powinien przerwać się z powodu niespełnionej asercji:
unsigned long dict;
char buf[4] = "foo";
dict = dict_new();
dict_insert(dict, buf, buf);
buf[0] = 'b';
assert(dict_find(dict, "foo") != NULL);
assert(strncmp(dict_find(dict, "foo"), "foo", 4) == 0);
assert(strncmp(dict_find(dict, "foo"), "boo", 4) != 0);
Należy ukryć przed światem zewnętrznym wszystkie zmienne globalne i funkcje pomocnicze nienależące do wyspecyfikowanego interfejsu modułu.
W rozwiązaniu nie należy nadużywać kompilacji warunkowej. Fragmenty tekstu źródłowego realizujące wyspecyfikowane operacje na słownikach nie powinny zależeć od sposobu kompilacji – parametr -DNDEBUG
lub jego brak (inaczej posiadanie wersji diagnostycznej nie miałoby sensu).
Uwaga! W rozwiązaniu zadania obsługa standardowego wyjścia błędów powinna być realizowana z użyciem strumieni C++ (tzn. biblioteki iostream
).
Rozwiązanie powinno zawierać pliki dict.h
, dict.cc
, dictglobal.h
, dictglobal.cc
, cdict
, cdictglobal
. Pliki te należy umieścić w repozytorium w katalogu
grupaN/zadanie2/ab123456+cd123456
lub
grupaN/zadanie2/ab123456+cd123456+ef123456
gdzie N
jest numerem grupy, a ab123456
, cd123456
, ef123456
są identyfikatorami członków zespołu umieszczającego to rozwiązanie.
Katalog z rozwiązaniem nie powinien zawierać innych plików, ale może zawierać podkatalog private, gdzie można umieszczać różne pliki, np. swoje testy. Pliki umieszczone w tym podkatalogu nie będą oceniane.