User Tools

Site Tools


cpp:copy_constructors

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Last revisionBoth sides next revision
cpp:copy_constructors [2020/04/14 14:32] – [Κατασκευαστές Αντιγραφείς] gthanoscpp:copy_constructors [2022/05/12 19:08] – [Μια πιο σύνθετη περίπτωση] gthanos
Line 1: Line 1:
 ====== Κατασκευαστές Αντιγραφείς ====== ====== Κατασκευαστές Αντιγραφείς ======
  
-Στην ενότητα των συναρτήσεων είδαμε [[cpp:functions#κλήση_με_τιμή_και_κλήση_με_αναφορά|την κλήση με τιμή και κλήση με αναφορά]] προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά **την κλήση με τιμή** όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο //stack// της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα (//copy constructor//), ο οποίος δημιουργεί ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου. Δείτε το παρακάτω παράδειγμα της μεθόδου //printArea// η οποία λαμβάνει ως παράμετρο ένα αντικείμενο της κλάσης //Rectangle//.+Στην ενότητα των συναρτήσεων είδαμε [[cpp:functions#κλήση_με_τιμή_και_κλήση_με_αναφορά|την κλήση με τιμή και κλήση με αναφορά]] προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά **την κλήση με τιμή** όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο //stack// της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα (//copy constructor//), ο οποίος δημιουργεί ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου του ίδου τύπου.  
 + 
 +Δείτε το παρακάτω παράδειγμα της μεθόδου //printArea// η οποία λαμβάνει ως παράμετρο ένα αντικείμενο της κλάσης //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(); 
 +    void setWidth(int w); 
 +    void setHeight(int h); 
 +    int getWidth(); 
 +    int getHeight(); 
 +}; 
 +</code> 
 + 
 +<code cpp Rectangle.cpp> 
 +#include "Rectangle.hpp" 
 + 
 +Rectangle::Rectangle(int w, int h) { 
 +  cout << "Calling 2 args constructor" << endl; 
 +  width = w; height = h; 
 +
 + 
 +Rectangle::Rectangle(int s) { 
 +  cout << "Calling 1 args constructor" << endl; 
 +  width = s; height = s; 
 +
 + 
 +Rectangle::Rectangle() { 
 +  cout << "Calling default constructor" << endl; 
 +  width = height = 0; 
 +
 + 
 +void Rectangle::setWidth(int w) { width = w; } 
 +void Rectangle::setHeight(int h) { height = h; } 
 +int Rectangle::getWidth() { return width; } 
 +int Rectangle::getHeight() { return height; } 
 +</code>
  
 <code cpp foo.cpp> <code cpp foo.cpp>
Line 21: Line 67:
  
 {{ :cpp:cppstackcopyconstructor.png |}} {{ :cpp:cppstackcopyconstructor.png |}}
 +
 +Το ερώτημα είναι με ποιό τρόπο γίνεται η δημιουργία του αντιγράφου του αρχικού αντικειμένου στο //stack//. Μπορείτε να δηλώσετε τον δικό σας κατασκευαστή αντιγραφέα (λεπτομέρειες πιο κάτω..) ή να αφήσετε τον //compiler// να δηλώσει τον //default//. Ο //default// κατασκευαστής αντιγραφέας δημιουργεί το νέο αντικείμενο από το παλιό αντιγράφοντας τα πεδία ένα προς ένα. Εάν αυτός ο τρόπος σας ικανοποιεί, τότε δεν χρειάζεται να κάνετε κάτι περισσότερο. Εάν όμως η παραπάνω μεθοδολογία δεν είναι ικανοποιητική, είστε αναγκασμένοι να ορίσετε τον δικό σας κατασκευαστή αντιγραφέα.
  
 ===== Ορισμός ενός κατασκευαστή αντιγραφέα ===== ===== Ορισμός ενός κατασκευαστή αντιγραφέα =====
  
-Ένας κατασκευαστής αντιγραφέας για την κλάση Rectangle θα μπορούσε να είναι ο εξής:+Ένας κατασκευαστής αντιγραφέας για την παραπάνω κλάση //Rectangle// θα μπορούσε να είναι ο εξής:
  
 <code cpp> <code cpp>
 Rectangle::Rectangle(Rectangle &r) { Rectangle::Rectangle(Rectangle &r) {
-  width = r.width; height = r.height;+  width = r.width;  
 +  height = r.height;
 } }
 </code> </code>
-ή ο παρακάτω+ή ισοδύναμα ο παρακάτω
 <code cpp> <code cpp>
 Rectangle::Rectangle(const Rectangle &r) { Rectangle::Rectangle(const Rectangle &r) {
-  width = r.width; height = r.height;+  width = r.width;  
 +  height = r.height;
 } }
 </code> </code>
  
-Στη 2η περίπτωση, η μεταβλητή //r// δηλώνεται ως //const// διότι κατά την εκτέλεση του κατασκευαστή αντιγραφέα το αντικείμενο //r// δεν μεταβάλλεται. Συνολικά η κλάση //Rectangle// διαμορφώνεται ως εξής:+Και οι δύο παραπάνω κατασκευαστές είναι ισοδύναμοι και δηλώνουν ένα κατασκευαστή αντιγραφέα. Η μεταβλητή //r// μπορεί να δηλωθεί ως //const// (όπως στη δεύτερη περίπτωση), διότι κατά την εκτέλεση του κατασκευαστή αντιγραφέα το αντικείμενο //r// δεν μεταβάλλεται. Συνολικάη κλάση //Rectangle// διαμορφώνεται ως εξής:
  
 <code cpp Rectangle.hpp> <code cpp Rectangle.hpp>
Line 53: Line 103:
     Rectangle(int s);     Rectangle(int s);
     Rectangle();     Rectangle();
-    Rectangle(Rectangle &r);+    Rectangle(const Rectangle& r);
     void setWidth(int w);     void setWidth(int w);
     void setHeight(int h);     void setHeight(int h);
Line 59: Line 109:
     int getHeight();     int getHeight();
 }; };
 +</code>
 +
 +<code cpp Rectangle.cpp>
 +#include "Rectangle.hpp"
  
 Rectangle::Rectangle(int w, int h) { Rectangle::Rectangle(int w, int h) {
Line 65: Line 119:
 } }
  
-Rectangle::Rectangle(int s) : Rectangle(s,s) {+Rectangle::Rectangle(int s) {
   cout << "Calling 1 args constructor" << endl;   cout << "Calling 1 args constructor" << endl;
 +  width = s; height = s;
 } }
  
-Rectangle::Rectangle() : Rectangle(0,0) {+Rectangle::Rectangle() {
   cout << "Calling default constructor" << endl;   cout << "Calling default constructor" << endl;
 +  width = height = 0;
 } }
  
-Rectangle::Rectangle(Rectangle &r) {+Rectangle::Rectangle(const Rectangle& r) {
   cout << "Calling copy constructor" << endl;   cout << "Calling copy constructor" << endl;
-  width = r.width; height = r.height;+  width = r.width; 
 +  height = r.height;
 } }
  
Line 82: Line 139:
 int Rectangle::getWidth() { return width; } int Rectangle::getWidth() { return width; }
 int Rectangle::getHeight() { return height; } int Rectangle::getHeight() { return height; }
- 
-void printArea(Rectangle r) { 
-  cout << " area: " << r.getWidth() * r.getHeight() << endl; 
-} 
 </code> </code>
  
Line 92: Line 145:
 </WRAP> </WRAP>
  
-==== Άλλη περίπτωση κλήση Copy Constructor ====+==== Άλλη περίπτωση κλήσης κατασκευαστή αντιγραφέα ====
  
-Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. Εδώ η δήλωση της μεταβλητής r2 συμπίπτει με την αρχικοποίηση του αντικειμένου. Σε αυτή την περίπτωση καλείται ο κατασκευαστής αντιγραφέας με όρισμα το //r1//+Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. 
  
 <code cpp CopyRectangle.cpp> <code cpp CopyRectangle.cpp>
Line 104: Line 157:
 } }
 </code> </code>
 +
 +Εδώ η δήλωση της μεταβλητής r2 συμπίπτει με την αρχικοποίηση του αντικειμένου. Σε αυτή την περίπτωση καλείται ο κατασκευαστής αντιγραφέας με όρισμα το //r1//
  
 <WRAP center round tip 80%> <WRAP center round tip 80%>
-Το παραπάνω είναι λειτουργικά ισοδύναμο με το παρακάτω.+Το παραπάνω είναι λειτουργικά ισοδύναμο με το εξής:
 <code cpp CopyRectangle.cpp> <code cpp CopyRectangle.cpp>
 #include "Rectangle.hpp" #include "Rectangle.hpp"
Line 116: Line 171:
 } }
 </code> </code>
-Για τον μεταγλωττιστή όμως οι κώδικες είναι διαφορετικοί. Στην πρώτη περίπτωση καλείται ο κατασκευαστής αντιγραφέας (//copy constructor//), ενώ στη 2η περίπτωση καλείται ο //default// κατασκευαστής και στη συνέχεια γίνεται ανάθεση των τιμών των πεδίων του //r1// στα πεδία του //r2// (πεδίο προς πεδίο).+Για τον μεταγλωττιστή όμως οι δύο κώδικες είναι διαφορετικοί. Στην πρώτη περίπτωση καλείται ο κατασκευαστής αντιγραφέας (//copy constructor//), ενώ στη δεύτερη περίπτωση καλείται ο //default// κατασκευαστής και στη συνέχεια γίνεται ανάθεση των τιμών των πεδίων του //r1// στα πεδία του //r2// (πεδίο προς πεδίο)
 + 
 +**Σημείωση:** Κάποιες νεότερες εκδόσεις του μεταγλωττιστή εισάγουν την κλήση του κατασκευαστή αντιγραφέα και σε αυτή την περίπτωση με σκοπό τη βελτίωση της επίδοσης.
 </WRAP> </WRAP>
  
-===== Μια πιο σύνθετη περίπτωση =====+===== Πιο σύνθετες περιπτώσεις ===== 
 + 
 +/* 
 +==== 1η Περίπτωση - Αρχικοποίηση ενός αντικειμένου από άλλο αντικείμενο στην ίδια συνάρτηση ==== 
 +*/
  
-Στις περιπτώσεις που υπάρχουν πεδία δείκτες που δείχνουν σε άλλα αντικείμενα (στατικά ή δυναμικά δεσμευμένα) αντιγράφονται οι διευθύνσεις αυτές, όπως θα αντιγράφονταν οποιοδήποτε άλλο πεδίο. Αυτό πρακτικά σημαίνει ότι δύο ή περισσότερα αντικείμενα δείχνουν σε μία κοινή περιοχή μνήμης. Το παραπάνω μπορεί να προκαλέσει δυσλειτουργίες, καθώς η μεταβολή του κοινού αντικειμένου επηρεάζει το σύνολο των αντικειμένων που το μοιράζονται.+Στις περιπτώσεις που υπάρχουν πεδία που είναι δείκτες και δείχνουν σε άλλα αντικείμενα (στατικά ή δυναμικά δεσμευμένα) εάν δεν ορίσετε τον δικό σας κατασκευαστή αντιγραφέα, ο //default// αντιγράφει τις διευθύνσεις αυτές πεδίο προς πεδίο, όπως θα αντιγράφονταν οποιοδήποτε άλλο πεδίο. Αυτό πρακτικά σημαίνει ότι δύο ή περισσότερα αντικείμενα δείχνουν σε μία κοινή περιοχή μνήμης μέσω των αντίστοιχων πεδίων τους. Το παραπάνω μπορεί να προκαλέσει δυσλειτουργίες, καθώς η μεταβολή του περιεχομένου της κοινής μνήμης επηρεάζει το σύνολο των αντικειμένων που το μοιράζονται. Ενδεικτικό είναι το παρακάτω παράδειγμα.
  
 Στο παρακάτω παράδειγμα ορίζουμε την κλάση //Point// η οποία αντιπροσωπεύει ένα σημείο στο δισδιάστατο χώρο. Στο παρακάτω παράδειγμα ορίζουμε την κλάση //Point// η οποία αντιπροσωπεύει ένα σημείο στο δισδιάστατο χώρο.
Line 151: Line 212:
     void setX(int vx) { x = vx; }     void setX(int vx) { x = vx; }
     void setY(int vy) { y = vy; }     void setY(int vy) { y = vy; }
-    int getX() { return x; } +    int getX() const { return x; } 
-    int getY() { return y; }+    int getY() const { return y; }
 }; };
 </code> </code>
Line 175: Line 236:
     Rectangle();     Rectangle();
     ~Rectangle();     ~Rectangle();
-    Rectangle(Rectangle &r); 
     void setWidth(int w);     void setWidth(int w);
     void setHeight(int h);     void setHeight(int h);
Line 181: Line 241:
     int getHeight();     int getHeight();
     void setOrigin(Point *p);     void setOrigin(Point *p);
-    Point *getOrigin();+    Point *getOrigin() const;
 }; };
  
Line 211: Line 271:
 int Rectangle::getHeight() { return height; } int Rectangle::getHeight() { return height; }
 void Rectangle::setOrigin(Point *p) { origin = p; } void Rectangle::setOrigin(Point *p) { origin = p; }
-Point *Rectangle::getOrigin() { return origin; }+Point *Rectangle::getOrigin() const { return origin; }
 </code> </code>
  
Line 224: Line 284:
  
 int main() { int main() {
-  Point p{10,5};+  Point p{5,5};
   Rectangle r1{5,6,p};     Rectangle r1{5,6,p};  
   Rectangle r2 = r1;   Rectangle r2 = r1;
 +  Rectangle r3(r1);
      
   moveOrigin(r1, 1,-1);   moveOrigin(r1, 1,-1);
Line 232: Line 293:
 </code> </code>
  
-Ο παραπάνω κώδικας αντιγράφει στο αντικείμενο //r2// τα πεδία του αντικειμένου //r1// πεδίο προς πεδίο. Αυτό σημαίνει ότι τα αντικείμενα //r1// και //r2// μοιράζονται το ίδιο αντικείμενο τύπου //Point//. Ισχύουν επομένως τα εξής:+Ο παραπάνω κώδικας δεν περιέχει κατασκευαστή αντιγραφέα για την κλάση Rectangle. Ο //default// που δημιουργεί ο //compiler//, αντιγράφει στο αντικείμενο //r2// τα πεδία του αντικειμένου //r1// πεδίο προς πεδίο. Αυτό σημαίνει ότι τα αντικείμενα //r1// και //r2// μοιράζονται το ίδιο αντικείμενο τύπου //Point//. Ισχύουν επομένως τα εξής:
   * Εάν μεταβληθούν οι συντεταγμένες του //Point// από το αντικείμενο //r1//, η μεταβολή θα ισχύει και για το αντικείμενο //r2//   * Εάν μεταβληθούν οι συντεταγμένες του //Point// από το αντικείμενο //r1//, η μεταβολή θα ισχύει και για το αντικείμενο //r2//
-  * Κατά την έξοδο από τη συνάρτηση main, το αντικείμενο //r1// θα καταστραφεί ελευθερώνοντας τη δεσμευμένη μνήμη για το πεδίο του //origin//. Η προσπάθεια καταστρροφής του αντικειμένου //r2// θα οδηγήσει σε σφάλμα διότι θα προσπαθήσει να ελευθερώσει μία περιοχή μνήμης που έχει ήδη ελευθερωθεί κατά την καταστροφή του //r1//. Το σφάλμα που εκτυπώνεται όταν το πρόγραμμα εκτελεστεί είναι το εξής:+  * Κατά την έξοδο από τη συνάρτηση main, το αντικείμενο //r1// θα καταστραφεί ελευθερώνοντας τη δεσμευμένη μνήμη για το πεδίο του //origin//. Η προσπάθεια καταστροφής του αντικειμένου //r2// θα οδηγήσει σε σφάλμα διότι θα προσπαθήσει να ελευθερώσει μία περιοχή μνήμης που έχει ήδη ελευθερωθεί κατά την καταστροφή του //r1//. Το σφάλμα που εκτυπώνεται όταν το πρόγραμμα εκτελεστεί είναι το εξής:
  
 <code> <code>
Line 247: Line 308:
 Ο προτεινόμενος κατασκευαστής αντιγραφέας δίνεται παρακάτω: Ο προτεινόμενος κατασκευαστής αντιγραφέας δίνεται παρακάτω:
 <code cpp> <code cpp>
-Rectangle::Rectangle(Rectangle &r) {+Rectangle::Rectangle(const Rectangle &r) {
   width = r.width;   width = r.width;
   height = r.height;   height = r.height;
Line 261: Line 322:
 } }
 </code>  </code> 
- 
- 
- 
- 
- 
- 
- 
  
cpp/copy_constructors.txt · Last modified: 2022/05/12 19:41 by gthanos