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
Next revisionBoth sides next revision
cpp:copy_constructors [2017/04/20 11:44] gthanoscpp:copy_constructors [2020/04/08 14:51] – [Μια πιο σύνθετη περίπτωση] gthanos
Line 8: Line 8:
 #include "Rectangle.cpp" #include "Rectangle.cpp"
  
-void printArea(Rectangle r) {+void printArea(const Rectangle r) {
   cout << " area: " << r.getArea() << endl;   cout << " area: " << r.getArea() << endl;
 } }
Line 24: Line 24:
 ===== Ορισμός ενός κατασκευαστή αντιγραφέα ===== ===== Ορισμός ενός κατασκευαστή αντιγραφέα =====
  
-Ένας κατασκευαστής αντιγραφέας για την κλάση Rectangle ορίζεται ως εξής:+Ένας κατασκευαστής αντιγραφέας για την κλάση Rectangle θα μπορούσε να είναι ο εξής:
  
 <code cpp> <code cpp>
 Rectangle::Rectangle(Rectangle &r) { Rectangle::Rectangle(Rectangle &r) {
 +  width = r.width; height = r.height;
 +}
 +</code>
 +ή ο παρακάτω
 +<code cpp>
 +Rectangle::Rectangle(const Rectangle &r) {
   width = r.width; height = r.height;   width = r.width; height = r.height;
 } }
 </code> </code>
  
-Συνολικά η κλάση //Rectangle// διαμορφώνεται ως εξής:+Στη 2η περίπτωση, η μεταβλητή //r// δηλώνεται ως //const// διότι κατά την εκτέλεση του κατασκευαστή αντιγραφέα το αντικείμενο //r// δεν μεταβάλλεται. Συνολικά η κλάση //Rectangle// διαμορφώνεται ως εξής:
  
-<code cpp Rectangle.cpp>+<code cpp Rectangle.hpp>
 #include <iostream> #include <iostream>
 #include <cstdlib> #include <cstdlib>
Line 47: Line 53:
     Rectangle(int s);     Rectangle(int s);
     Rectangle();     Rectangle();
 +    Rectangle(Rectangle &r);
     void setWidth(int w);     void setWidth(int w);
     void setHeight(int h);     void setHeight(int h);
Line 54: Line 61:
  
 Rectangle::Rectangle(int w, int h) { Rectangle::Rectangle(int w, int h) {
 +  cout << "Calling 2 args constructor" << endl;
   width = w; height = h;   width = w; height = h;
 } }
  
-Rectangle::Rectangle(int s) { +Rectangle::Rectangle(int s) : Rectangle(s,s) { 
-  width = s; height = s;+  cout << "Calling 1 args constructor" << endl;
 } }
  
-Rectangle::Rectangle() +Rectangle::Rectangle() : Rectangle(0,0{ 
-  srand(time(NULL)); +  cout << "Calling default constructor" << endl;
-  width = rand() % 10 + 1; height = rand() % 10 + 1;+
 } }
  
-// copy constructor 
 Rectangle::Rectangle(Rectangle &r) { Rectangle::Rectangle(Rectangle &r) {
 +  cout << "Calling copy constructor" << endl;
   width = r.width; height = r.height;   width = r.width; height = r.height;
 } }
Line 75: Line 82:
 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>
 +
 +<WRAP center round tip 80%>
 +Εάν δεν ορίσετε ένα δικό σας κατασκευαστή αντιγραφέα ο //compiler// δημιουργεί τον //default copy constructor//. Ο //default// αντιγράφει τα περιεχόμενα του αντικειμένου που δίνεται ως όρισμα στο νέο αντικείμενο πεδίο προς πεδίο.
 +</WRAP>
 +
 +==== Άλλη περίπτωση κλήση Copy Constructor ====
 +
 +Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. Εδώ η δήλωση της μεταβλητής r2 συμπίπτει με την αρχικοποίηση του αντικειμένου. Σε αυτή την περίπτωση καλείται ο κατασκευαστής αντιγραφέας με όρισμα το //r1//
 +
 +<code cpp CopyRectangle.cpp>
 +#include "Rectangle.cpp"
 +
 +int main() {
 +  Rectangle r1(5,6);
 +  Rectangle r2 = r1;
 +}
 +</code>
 +
 +<WRAP center round tip 80%>
 +Το παραπάνω είναι λειτουργικά ισοδύναμο με το παρακάτω.
 +<code cpp CopyRectangle.cpp>
 +#include "Rectangle.cpp"
 +
 +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