User Tools

Site Tools


cpp:copy_constructors

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
Next revisionBoth sides next revision
cpp:copy_constructors [2017/04/20 10:13] – created gthanoscpp:copy_constructors [2020/04/12 16:02] gthanos
Line 1: Line 1:
 ====== Κατασκευαστές Αντιγραφείς ====== ====== Κατασκευαστές Αντιγραφείς ======
  
-Στην ενότητα των συναρτήσεων είδαμε [[cpp:functions#κλήση_με_τιμή_και_κλήση_με_αναφορά|την κλήση με τιμή και κλήση με αναφορά]] προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά την κλήση με τιμή όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο //stack// της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα ο οποίος έχει την δυνατότητα να δημιουργήσει ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου. Δείτε το παρακάτω παράδειγμα της μεθόδου //foo// η οποία λαμβάνει ένα αντικείμενο της κλάσης //Rectangle//.+Στην ενότητα των συναρτήσεων είδαμε [[cpp:functions#κλήση_με_τιμή_και_κλήση_με_αναφορά|την κλήση με τιμή και κλήση με αναφορά]] προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά την κλήση με τιμή όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο //stack// της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα (//copy constructor//), ο οποίος δημιουργεί ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου. Δείτε το παρακάτω παράδειγμα της μεθόδου //printArea// η οποία λαμβάνει ως παράμετρο ένα αντικείμενο της κλάσης //Rectangle//
 + 
 +<code cpp foo.cpp> 
 +#include <iostream> 
 +using namespace std; 
 +#include "Rectangle.hpp" 
 + 
 +void printArea(const Rectangle r) { 
 +  cout << " area: " << r.getArea() << endl; 
 +
 + 
 +int main() { 
 +  Rectangle rect(5,6); 
 +  printArea(rect); 
 +
 +</code> 
 + 
 +Παρακάτω δίνεται το σχηματικό διάγραμμα της στοίβας της διεργασίας πριν και κατά τη διάρκεια της κλήσης της μεθόδου //printArea//. Κατά την κλήση της συνάρτησης //printArea//, διακρίνεται η αντιγραφή του αντικειμένου //rect// στη μεταβλητή //r// που ανήκει στη στοίβα της //printArea//
 + 
 +{{ :cpp:cppstackcopyconstructor.png |}} 
 + 
 +===== Ορισμός ενός κατασκευαστή αντιγραφέα ===== 
 + 
 +Ένας κατασκευαστής αντιγραφέας για την κλάση Rectangle θα μπορούσε να είναι ο εξής: 
 + 
 +<code cpp> 
 +Rectangle::Rectangle(Rectangle &r) { 
 +  width = r.width; height = r.height; 
 +
 +</code> 
 +ή ο παρακάτω 
 +<code cpp> 
 +Rectangle::Rectangle(const Rectangle &r) { 
 +  width = r.width; height = r.height; 
 +
 +</code> 
 + 
 +Στη 2η περίπτωση, η μεταβλητή //r// δηλώνεται ως //const// διότι κατά την εκτέλεση του κατασκευαστή αντιγραφέα το αντικείμενο //r// δεν μεταβάλλεται. Συνολικά η κλάση //Rectangle// διαμορφώνεται ως εξής: 
 + 
 +<code cpp Rectangle.hpp> 
 +#include <iostream> 
 +#include <cstdlib> 
 +#include <ctime> 
 +using namespace std; 
 + 
 +class Rectangle { 
 +  private: 
 +    int width, height; 
 +  public: 
 +    Rectangle(int w, int h); 
 +    Rectangle(int s); 
 +    Rectangle(); 
 +    Rectangle(Rectangle &r); 
 +    void setWidth(int w); 
 +    void setHeight(int h); 
 +    int getWidth(); 
 +    int getHeight(); 
 +}; 
 + 
 +Rectangle::Rectangle(int w, int h) { 
 +  cout << "Calling 2 args constructor" << endl; 
 +  width = w; height = h; 
 +
 + 
 +Rectangle::Rectangle(int s) : Rectangle(s,s) { 
 +  cout << "Calling 1 args constructor" << endl; 
 +
 + 
 +Rectangle::Rectangle() : Rectangle(0,0) { 
 +  cout << "Calling default constructor" << endl; 
 +
 + 
 +Rectangle::Rectangle(Rectangle &r) { 
 +  cout << "Calling copy constructor" << endl; 
 +  width = r.width; height = r.height; 
 +
 + 
 +void Rectangle::setWidth(int w) { width = w; } 
 +void Rectangle::setHeight(int h) { height = h; } 
 +int Rectangle::getWidth() { return width; } 
 +int Rectangle::getHeight() { return height; } 
 + 
 +void printArea(Rectangle r) { 
 +  cout << " area: " << r.getWidth() * r.getHeight() << endl; 
 +
 +</code> 
 + 
 +<WRAP center round tip 80%> 
 +Εάν δεν ορίσετε ένα δικό σας κατασκευαστή αντιγραφέα ο //compiler// δημιουργεί τον //default copy constructor//. Ο //default// αντιγράφει τα περιεχόμενα του αντικειμένου που δίνεται ως όρισμα στο νέο αντικείμενο πεδίο προς πεδίο. 
 +</WRAP> 
 + 
 +==== Άλλη περίπτωση κλήση Copy Constructor ==== 
 + 
 +Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. Εδώ η δήλωση της μεταβλητής r2 συμπίπτει με την αρχικοποίηση του αντικειμένου. Σε αυτή την περίπτωση καλείται ο κατασκευαστής αντιγραφέας με όρισμα το //r1//.  
 + 
 +<code cpp CopyRectangle.cpp> 
 +#include "Rectangle.hpp" 
 + 
 +int main() { 
 +  Rectangle r1(5,6); 
 +  Rectangle r2 = r1; 
 +
 +</code> 
 + 
 +<WRAP center round tip 80%> 
 +Το παραπάνω είναι λειτουργικά ισοδύναμο με το παρακάτω. 
 +<code cpp CopyRectangle.cpp> 
 +#include "Rectangle.hpp" 
 + 
 +int main() { 
 +  Rectangle r1(5,6); 
 +  Rectangle r2; 
 +  r2 = r1; 
 +
 +</code> 
 +Για τον μεταγλωττιστή όμως οι κώδικες είναι διαφορετικοί. Στην πρώτη περίπτωση καλείται ο κατασκευαστής αντιγραφέας (//copy constructor//), ενώ στη 2η περίπτωση καλείται ο //default// κατασκευαστής και στη συνέχεια γίνεται ανάθεση των τιμών των πεδίων του //r1// στα πεδία του //r2// (πεδίο προς πεδίο). 
 +</WRAP> 
 + 
 +===== Μια πιο σύνθετη περίπτωση ===== 
 + 
 +Στις περιπτώσεις που υπάρχουν πεδία δείκτες που δείχνουν σε άλλα αντικείμενα (στατικά ή δυναμικά δεσμευμένα) αντιγράφονται οι διευθύνσεις αυτές, όπως θα αντιγράφονταν οποιοδήποτε άλλο πεδίοΑυτό πρακτικά σημαίνει ότι δύο ή περισσότερα αντικείμενα δείχνουν σε μία κοινή περιοχή μνήμης. Το παραπάνω μπορεί να προκαλέσει δυσλειτουργίες, καθώς η μεταβολή του κοινού αντικειμένου επηρεάζει το σύνολο των αντικειμένων που το μοιράζονται. 
 + 
 +Στο παρακάτω παράδειγμα ορίζουμε την κλάση //Point// η οποία αντιπροσωπεύει ένα σημείο στο δισδιάστατο χώρο. 
 + 
 +<code cpp Point.hpp> 
 +#include <iostream> 
 +using namespace std; 
 + 
 +class Point { 
 +    int x, y; 
 +  public: 
 +    Point(int vx,int vy) {  
 +      x = vx; y = vy;  
 +      cout << "Point regular constructor!\n"; 
 +    } 
 +     
 +    Point(const Point &p) {  
 +      x = p.x; y = p.y;  
 +      cout << "Point copy constructor!\n"; 
 +    } 
 +     
 +    Point() {  
 +      cout << "Point default constructor!\n"; 
 +    } 
 +    ~Point() {  
 +      cout << "xP Point destructor!\n";  
 +    } 
 +     
 +    void setX(int vx) { x = vx; } 
 +    void setY(int vy) { y = vy; } 
 +    int getX() { return x; } 
 +    int getY() { return y; } 
 +}; 
 +</code> 
 + 
 +Η κλάση //Rectangle// που ακολουθεί ορίζει το πεδίο //origin// που είναι δείκτης σε ένα αντικείμενο τύπου //Point//. Η δημιουργία ενός αντικειμένου τύπου //Rectangle// συνεπάγεται τη δυναμική δέσμευση μνήμης για το αντικείμενο τύπου //Point// που αυτή περιέχει. Δείτε το παράδειγμα που ακολουθεί. 
 + 
 +<code cpp Rectangle.hpp> 
 +#include <iostream> 
 +#include <cstdlib> 
 +#include <ctime> 
 +using namespace std; 
 + 
 +#include "Point.hpp" 
 + 
 +class Rectangle { 
 +  private: 
 +    int width, height; 
 +    Point *origin; 
 +  public: 
 +    Rectangle(int w, int h, Point p); 
 +    Rectangle(int s, Point p); 
 +    Rectangle(); 
 +    ~Rectangle(); 
 +    Rectangle(Rectangle &r); 
 +    void setWidth(int w); 
 +    void setHeight(int h); 
 +    int getWidth(); 
 +    int getHeight(); 
 +    void setOrigin(Point *p); 
 +    Point *getOrigin(); 
 +}; 
 + 
 +Rectangle::Rectangle(int w, int h, Point p) { 
 +  width = w; height = h; 
 +  origin = new (nothrow) Point( p.getX(), p.getY() ); 
 +  if(origin == NULL) { 
 +    cerr << "Memory allocation failure!\n"; 
 +    exit(-1); 
 +  } 
 +  cout << "Calling 2 args constructor" << endl; 
 +
 + 
 +Rectangle::Rectangle(int s, Point p) : Rectangle(s,s,p) { 
 +  cout << "Calling 1 args constructor" << endl; 
 +
 + 
 +Rectangle::Rectangle() : Rectangle(0,Point()) { 
 +  cout << "Calling 0 args constructor" << endl; 
 +
 + 
 +Rectangle::~Rectangle() { 
 +  delete origin; 
 +
 + 
 +void Rectangle::setWidth(int w) { width = w; } 
 +void Rectangle::setHeight(int h) { height = h; } 
 +int Rectangle::getWidth() { return width; } 
 +int Rectangle::getHeight() { return height; } 
 +void Rectangle::setOrigin(Point *p) { origin = p; } 
 +Point *Rectangle::getOrigin() { return origin; } 
 +</code> 
 + 
 +<code cpp MoveOrigin.cpp> 
 +#include "Rectangle.hpp" 
 + 
 +int moveOrigin(Rectangle &r, int dx, int dy) { 
 +  Point *p = r.getOrigin(); 
 +  p->setX(p->getX() + dx); 
 +  p->setY(p->getY() + dy); 
 +
 + 
 +int main() { 
 +  Point p{10,5}; 
 +  Rectangle r1{5,6,p};   
 +  Rectangle r2 = r1; 
 +   
 +  moveOrigin(r1, 1,-1); 
 +
 +</code> 
 + 
 +Ο παραπάνω κώδικας αντιγράφει στο αντικείμενο //r2// τα πεδία του αντικειμένου //r1// πεδίο προς πεδίο. Αυτό σημαίνει ότι τα αντικείμενα //r1// και //r2// μοιράζονται το ίδιο αντικείμενο τύπου //Point//. Ισχύουν επομένως τα εξής: 
 +  * Εάν μεταβληθούν οι συντεταγμένες του //Point// από το αντικείμενο //r1//, η μεταβολή θα ισχύει και για το αντικείμενο //r2//.  
 +  * Κατά την έξοδο από τη συνάρτηση main, το αντικείμενο //r1// θα καταστραφεί ελευθερώνοντας τη δεσμευμένη μνήμη για το πεδίο του //origin//. Η προσπάθεια καταστρροφής του αντικειμένου //r2// θα οδηγήσει σε σφάλμα διότι θα προσπαθήσει να ελευθερώσει μία περιοχή μνήμης που έχει ήδη ελευθερωθεί κατά την καταστροφή του //r1//. Το σφάλμα που εκτυπώνεται όταν το πρόγραμμα εκτελεστεί είναι το εξής: 
 + 
 +<code> 
 +*** Error in `./a.out': double free or corruption (fasttop): 0x00000000006d6010 *** 
 +Ακυρώθηκε (core dumped) 
 +</code> 
 + 
 +Για να αποφύγετε την παραπάνω συμπεριφορά θα πρέπει να ορίσετε τον δικό σας κατασκευαστή αντιγραφέα που κάνει τα εξής: 
 +  - Δημιουργεί ένα νέο αντικείμενο τύπου //Point//. 
 +  - Αντιγράφει τα περιεχόμενα του παλιού αντικειμένου στο νέο. 
 + 
 +Ο προτεινόμενος κατασκευαστής αντιγραφέας δίνεται παρακάτω: 
 +<code cpp> 
 +Rectangle::Rectangle(Rectangle &r) { 
 +  width = r.width; 
 +  height = r.height; 
 +  if(r.origin == nullptr) 
 +    origin = nullptr; 
 +  else { 
 +    origin = new (nothrow) Point(r.getOrigin()->getX(), r.getOrigin()->getY()); 
 +    if(origin == NULL) { 
 +      cerr << "Memory allocation failure!"; 
 +      exit(-1); 
 +    } 
 +  } 
 +
 +</code>  
 + 
 + 
 + 
 + 
  
  
  
cpp/copy_constructors.txt · Last modified: 2022/05/12 19:41 by gthanos