2010 03 Tworzenie kopii obiektów [Programowanie C C ]
Programowanie C++ Tworzenie kopii obiektów Wzorzec prototypu Kopiowanie obiektów, czyli tworzenie duplikatów, przechowujących te same informacje bez niszczenia oryginału, jest jedną z podstawowych operacji, które wykorzystujemy w programowaniu. Artykuł opisuje tę czynność, analizując techniki wspierające proces tworzenia kopii w języku C++. ne powyżej. Na początku wykonujemy ko- Dowiesz się: Powinieneś wiedzieć: pię płytką, która jest przeprowadzana szyb- " Co to jest leniwe kopiowanie; " Jak pisać proste programy w C++; ko i umożliwia poprawne odczytywanie in- " Co to jest wzorzec prototypu; " Co to jest dziedziczenie i funkcje formacji przechowywanych w zarządzanym " Jak stworzyć fabrykę prototypów. wirtualne; obiekcie. Przy próbie modyfikacji obiektu " Co to są szablony. badamy, czy obiekt jest wskazywany przez jeden, czy przez kilka wskazników. Jeże- li istnieje tylko jeden wskaznik, to mody- fikacja odbywa się na zarządzanym obiek- kopiowanie obiektów wskazywanych. Two- cie, natomiast jeżeli wskazników jest wię- rzenie takiej kopii zajmuje więcej czasu i zaso- cej, wykonuje się głęboką kopię wskazywa- Poziom bów, ale obiekt i kopia są od siebie niezależne. nego obiektu i modyfikuje się tę kopię. Leni- trudności Zmiany obiektu nie mają wpływu na kopię. we kopiowanie umożliwia więc optymalne Na Rysunku 1 pokazano zawartość wskazni- połączenie obu strategii, a ceną jest koniecz- ków po wykonaniu płytkiej i głębokiej kopii, ność przechowywania dodatkowej składo- przykład kodu zawiera Listing 1. wej, która pozwala rozstrzygnąć, czy nale- opiowanie obiektów jest operacją ży robić głęboką kopię. Składową tą jest licz- wykonywaną bardzo często: prze- Kopiowanie opóznione nik odniesień lub flaga. Można pozbyć się Kkazując argumenty przez wartość, Kopiowanie opóznione lub leniwe wyko- tej składowej, tworząc głęboką kopię obiek- zwracając wyniki obliczeń, przechowując rzystuje obie strategie kopiowania opisa- tu za każdym razem, gdy wołana jest opera- elementy w kontenerach i w wielu innych sytuacjach są tworzone kopie. Jeżeli obiekt Listing 1. Tworzenie kopii płytkiej i głębokiej jest dostępny pośrednio, na przykład przez wskaznik, to można wykonać kopię wskazni- class Foo { //klasa pomocnicza ka (lub innego uchwytu) albo całego obiek- public: tu. W związku z tym możemy wyróżnić trzy Foo() : i_(0) {} rodzaje kopiowania: kopiowanie płytkie, gdy int get() const { return i_; } kopiujemy uchwyty (wskazniki), kopiowa- void set(int i) { i_ = i; } nie głębokie, gdy tworzymy kopię obiektu, }; oraz kopiowanie leniwe, które łączy kopiowa- Foo* shellCopy(Foo* f) { //płytka kopia nie płytkie i głębokie. Do demonstracji tych return f; //wskazniki pokazują na ten sam obiekt technik będziemy używali klasy Foo pokaza- } nej na Listingu 1. Foo* deepCopy(Foo* f){ //głęboka kopia Kopią płytką nazywa się kopiowanie jedy- return new Foo(*f); //wskazniki pokazują na różne obiekty nie obiektów pośredniczących, wskazników, } referencji, uchwytów itp. Kopia taka jest Foo* p1 = new Foo(); tworzona szybko, ponieważ wskaznik lub in- Foo* p2 = shellCopy(p1); //płytka kopia ny obiekt pośredniczący jest zazwyczaj ma- Foo* p3 = deepCopy(p1); //głęboka kopia łym obiektem. Po wykonaniu płytkiej kopii p1->set(2); //zmiana obiektu wskazywanego przez p1 ten sam obiekt jest dostępny z wielu miejsc, assert( p2->get() == 2 ); //obiekt wskazywany przez p2 został zmieniony obiekt wskazywany nie jest kopiowany, zmia- assert( p3->get() == 1 ); //obiekt wskazywany przez p3 nie został zmieniony na jego stanu będzie widoczna we wszystkich kopiach. Głęboka kopia oznacza rzeczywiste 12 3/2010 Wzorzec prototypu cja modyfikująca, ale wtedy wiele kopii jest cja fastDeepCopy, pokazana na Listingu 3, SDJ 2/2010), a my dysponujemy tylko ty- zbędnych. wykorzystuje dodatkowy argument, który pem interfejsu. Rzeczywisty typ obiektu Przykład leniwego kopiowania został jest tworzony w czasie kompilacji na podsta- może być inny. pokazany na Listingu 2. Przedstawiona wie informacji o typie. Jego wartość nie jest Wzorzec prototypu, nazywany też wir- tam klasa wykorzystuje sprytne wskazni- istotna, natomiast typ pozwala wybrać od- tualnym konstruktorem, opisany w książce ki boost::shared_ptr, które zostały omó- powiednią funkcję kopiującą. Jeżeli obiekty ,,Wzorce projektowe'' przez bandę czworga'' wione w SDJ 11/2009. Sprytne wskazniki mogą być kopiowane za pomocą funkcji ko- (Gamma, Helm, Johnson, Vlissides), pozwa- to szablony, które pozwalają automatycznie piującej fragmenty pamięci, to jest ona wo- la na tworzenie głębokiej kopii w takich przy- usuwać obiekt utworzony na stercie, prze- łana, w przeciwnym wypadku woła się kon- padkach. Pomysł polega na przeniesieniu od- chowują one i aktualizują licznik odnie- struktor kopiujący. Technika trejtów została powiedzialności za tworzenie obiektów do sień do wskazywanego obiektu. Szablony opisana w SDJ 11/2009. klas konkretnych, wykorzystując mechanizm te wspierają tworzenie leniwej kopii, dostar- funkcji wirtualnych. Klasa bazowa dostarcza czają metodę unique, która pokazuje, czy Wzorzec prototypu metody czysto wirtualnej, która jest nadpi- zarządzany obiekt jest wskazywany przez Jeżeli posługujemy się wskaznikiem lub re- sywana w klasach konkretnych (gdzie znany jeden, czy więcej wskazników. Metoda ta ferencją do klasy bazowej, to możemy wy- jest typ), więc można utworzyć głęboką kopię jest wykorzystana w klasie LazyFoo do roz- konać jedynie płytką kopię. Kopia głęboka obiektu. Przykład pokazano na Listingu 4, strzygania, czy można modyfikować bieżący jest niedostępna, ponieważ przy tworzeniu klasa bazowa Figure dostarcza metody czy- obiekt, czy raczej należy zrobić kopię. obiektu należy podać konkretny typ (patrz sto wirtualnej clone. Metoda ta jest nadpisy- Tworząc kopię głęboką obiektu tymcza- sowego, który będzie usunięty po zakoń- Listing 2. Leniwa kopia z użyciem boost::shared_ptr czeniu operacji kopiowania, można wyko- nać kopię płytką i nie usuwać tego obiek- class LazyFoo { //przechowuje leniwą kopię obiektu typu Foo (Listing 1) tu, co przypomina przeniesienie właściciela public: obiektu. Taki mechanizm dla wskazników LazyFoo(int i) : ptr_(new Foo(i) ) {} dostarcza std::auto_ptr (SDJ 11/2009), LazyFoo(const LazyFoo& l) : ptr_(l.ptr_) {} w ogólnym przypadku wymaga on wspar- int get() const { return ptr_->get(); } cia w języku. Takie wsparcie będzie dostar- void set(int i) { //metoda zmienia stan obiektu czone w nowym standardzie C++200x po- if(ptr_.unique() ) { ptr_->set(i); } //bada czy istnieje konieczność przez referencję do r-wartości, co pozwoli tworzenia kopii na implementację różnych konstruktorów else { ptr_ = PFoo(new Foo(i) ); } kopiujących. Używając konstruktora ko- } piującego do r-wartości, będzie można prze- private: nieść zawartość obiektu, unikniemy wtedy typedef shared_ptr PFoo; zbędnej kopii. PFoo ptr_; }; Szybkie kopiowanie głębokie Dla pewnych typów obiektów kopia głęboka Listing 3. Wykorzystanie klasy cech do wyboru algorytmu kopiowania może być wykonana bez użycia konstrukto- ra kopiującego za pomocą operacji kopiują- cych fragmenty pamięci. Obiekty, które bę- template //kopiowanie za pomocą memcpy dą w ten sposób kopiowane, nie mogą mieć T* doFastDeepCopy(const T* element, true_type) { składowych, które są wskaznikami, bo wska- char* mem = new char[sizeof(T)]; //przydziela pamięć zywane przez te składowe obiekty także bę- memcpy(mem, element, sizeof(T)); dą musiały być kopiowane przy tworze- return reinterpret_cast(mem);//zwraca obiekt odpowiedniego typu niu kopii głębokiej. Informacji o tym, czy } obiekt może być kopiowany za pomocą ko- template //woła konstruktor kopiujący piowania bajtów, dostarcza klasa cech (trejt) T* doFastDeepCopy(const T* element, false_type) { has_trivial_copy , który jest dostarcza- return new T(*element); ny przez bibliotekę boost::traits. Funk- } template //algorytm tworzenia kopii wykorzystuje trejty T* fastDeepCopy(const T* element) { return doFastDeepCopy(element, has_trivial_copy() ); //tworzy dodatkowy Szybki start argument Aby uruchomić przedstawione przykła- dy, należy mieć dostęp do kompilatora } C++ oraz edytora tekstu. Niektóre przy- kłady korzystają z udogodnień dostar- czanych przez biblioteki boost, warun- kiem ich uruchomienia jest instalacja bi- bliotek boost (w wersji 1.36 lub nowszej). Na wydrukach pominięto dołączanie od- powiednich nagłówków oraz udostęp- nianie przestrzeni nazw, pełne zródła do- łączono jako materiały pomocnicze. Rysunek 1. Kopia płytka i głęboka dla obiektów dostępnych pośrednio 3/2010 www.sdjournal.org 13 Programowanie C++ wana w klasach konkretnych, jeżeli ją będzie- Listing 4. Wzorzec prototypu my wołali, to będzie tworzona głęboka kopia class Figure {//klasa bazowa obiektu o odpowiednim typie. public: Jeżeli jest dostępny wirtualny konstruktor, virtual Figure* clone() const = 0;//wirtualny konstruktor możemy tworzyć głęboką kopię, wykorzy- virtual ~Figure(){} stując interfejs klasy bazowej, wołając meto- }; dę clone(). Listing 4 zawiera przykład, któ- class Square : public Figure {//klasa konkretna ry tworzy głęboką kopię kolekcji figur i wyko- public: rzystuje przedstawioną technikę. Square(int size) : size_(size) {} Square(const Square& sq) : size_(sq.size_) {} Fabryka prototypów Figure* clone() const { return new Square(*this); } //tworzy głęboką kopię Wzorzec prototypu możemy wykorzystać private: w fabryce, która będzie dostarczała obiek- int size_; tów danego typu, nazywanej fabryką pro- }; totypów. Fabryki są to klasy pośredniczą- class Circle : public Figure {//klasa konkretna ce w tworzeniu nowych obiektów, jeden public: z rodzajów fabryk został omówiony w SDJ Circle(int r) : r_(r) {} 2/2010. Fabryka prototypów przechowu- Circle(const Circle& c) : r_(c.r_) {} je obiekty wzorcowe, które będą kopiowa- Figure* clone() const { return new Circle(*this); }//tworzy głęboką kopię ne, jeżeli użytkownik zleci utworzenie no- private: wego obiektu. Fabryka taka pozwala two- int r_; rzyć obiektów różnych typów na podstawie }; identyfikatora, ponadto możemy nadać róż- ne identyfikatory obiektom tego samego ty- //przykład użycia wirtualnego konstruktora do tworzenia głębokiej kopii obiektów pu różniącym się stanem. Fabryki prototy- typedef vector