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 Figures; pów zazwyczaj zużywają więcej zasobów niż
Figures figures;//kolekcja figur, która będzie kopiowana fabryki obiektów, konieczne jest przechowy-
figures.push_back(new Square(2) ); wanie obiektów wzorcowych, na podstawie
figures.push_back(new Circle(3) ); których będą tworzone kopie. Przykład ta-
figures.push_back(new Circle(1) ); kiej fabryki pokazano na Listingu 5.
Figures copy; //głęboka kopia kolekcji figur Fabryki prototypów pozwalają wygodnie
for(Figures::const_iterator ii = figures.begin(); ii != figures.end(); ++ii) tworzyć obiekty z danej hierarchii klas, wy-
copy.push_back( (*ii)->clone() );//wykorzystuje wzorzec prototypu magają, aby w tej hierarchii był implemento-
wany wzorzec prototypu. Dodatkowym kosz-
tem tego rodzaju fabryki jest używanie me-
Listing 5. Fabryka prototypów
chanizmu póznego wiązania (funkcje wirtu-
alne), więc obiekty muszą zawierać wskaznik
class FigCloneFactory { //fabryka prototypów dla hierarchii figur na tablicę funkcji wirtualnych, wołanie meto-
public: dy clone() odbywa się pośrednio.
int registerFig(Figure* prototype, int id) { //rejestruje nowy obiekt oraz jego
identyfikator Podsumowanie
prototypes_.insert( make_pair(id, prototype) ); Przedstawione techniki związane z tworze-
} niem kopii są powszechnie stosowane w róż-
Figure* create(int id) {//tworzy obiekt danego typu i w danym stanie nych językach programowania. Ich znajo-
map::const_iterator i = prototypes_.find(id); mość pozwala na tworzenie płytkiej lub głę-
if(i ! = prototypes_.end() ) //jeżeli znalazł odpowiedni wpis bokiej kopii w zależności od potrzeb.
return prototypes_.find(id)->second->clone(); //wzorzec prototypu
return 0L; //zwraca nullptr jeżeli nie znalazł prototypu
}
W Sieci
private:
" http://www.boost.org  dokumentacja
map prototypes_;//przechowuje obiekty wzorcowe bibliotek boost;
" http://www.open-std.org  dokumenty
};
opisujące nowy standard C++.
R0BERT N0WAK
Więcej w książce
Adiunkt w Zakładzie Sztucznej Inteligencji Insty-
Zagadnienia dotyczące współcześnie stosowanych technik w języku C++, wzorce projekto-
tutu Systemów Elektronicznych Politechniki War-
we, programowanie generyczne, prawidłowe zarządzanie zasobami przy stosowaniu wy-
szawskiej, zainteresowany tworzeniem aplikacji
jątków, programowanie wielowątkowe, ilustrowane przykładami stosowanymi w bibliotece
standardowej i bibliotekach boost, zostały opisane w książce ,,Średnio zaawansowane pro- bioinformatycznych oraz zarządzania ryzykiem.
gramowanie w C++'', która ukaże się niebawem.
Programuje w C++ od ponad 10 lat.
Kontakt z autorem:rno@o2.pl
14 3/2010


Wyszukiwarka