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 [2021/05/07 08:32] – [Μια πιο σύνθετη περίπτωση] gthanos
Line 1: Line 1:
 ====== Κατασκευαστές Αντιγραφείς ====== ====== Κατασκευαστές Αντιγραφείς ======
  
-Στην ενότητα των συναρτήσεων είδαμε [[cpp:functions#κλήση_με_τιμή_και_κλήση_με_αναφορά|την κλήση με τιμή και κλήση με αναφορά]] προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά την κλήση με τιμή όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο //stack// της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα ο οποίος έχει την δυνατότητα να δημιουργήσει ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου. Δείτε το παρακάτω παράδειγμα της μεθόδου //foo// η οποία λαμβάνει ένα αντικείμενο της κλάσης //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();
 +};
 +
 +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>
 +#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 |}}
 +
 +Το ερώτημα είναι με ποιό τρόπο γίνεται η δημιουργία του αντιγράφου του αρχικού αντικειμένου στο //stack//. Μπορείτε να δηλώσετε τον δικό σας κατασκευαστή αντιγραφέα (λεπτομέρειες πιο κάτω..) ή να αφήσετε τον //compiler// να δηλώσει τον //default//. Ο //default// κατασκευαστής αντιγραφέας δημιουργεί το νέο αντικείμενο από το παλιό αντιγράφοντας τα πεδία πεδίο προς πεδιο. Εάν αυτός ο τρόπος σας ικανοποιεί, τότε δεν χρειάζεται να κάνετε κάτι περισσότερο. Εάν όμως η παραπάνω μεθοδολογία δεν είναι ικανοποιητική, είστε αναγκασμένοι να ορίσετε τον δικό σας κατασκευαστή αντιγραφέα.
 +
 +===== Ορισμός ενός κατασκευαστή αντιγραφέα =====
 +
 +Ένας κατασκευαστής αντιγραφέας για την παραπάνω κλάση //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>
 +
 +Και οι δύο παραπάνω κατασκευαστές είναι ισοδύναμοι και δηλώνουν ένα κατασκευαστή αντιγραφέα. Η μεταβλητή //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) {
 +  cout << "Calling 1 args constructor" << endl;
 +  width = s; height = s;
 +}
 +
 +Rectangle::Rectangle() {
 +  cout << "Calling default constructor" << endl;
 +  width = height = 0;
 +}
 +
 +Rectangle::Rectangle(const 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; }
 +</code>
 +
 +<WRAP center round tip 80%>
 +Εάν δεν ορίσετε ένα δικό σας κατασκευαστή αντιγραφέα ο //compiler// δημιουργεί τον //default copy constructor//. Ο //default// αντιγράφει τα περιεχόμενα του αντικειμένου που δίνεται ως όρισμα στο νέο αντικείμενο πεδίο προς πεδίο.
 +</WRAP>
 +
 +==== Άλλη περίπτωση κλήσης κατασκευαστή αντιγραφέα ====
 +
 +Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. 
 +
 +<code cpp CopyRectangle.cpp>
 +#include "Rectangle.hpp"
 +
 +int main() {
 +  Rectangle r1(5,6);
 +  Rectangle r2 = r1;
 +}
 +</code>
 +
 +Εδώ η δήλωση της μεταβλητής r2 συμπίπτει με την αρχικοποίηση του αντικειμένου. Σε αυτή την περίπτωση καλείται ο κατασκευαστής αντιγραφέας με όρισμα το //r1//
 +
 +<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//), ενώ στη δεύτερη περίπτωση καλείται ο //default// κατασκευαστής και στη συνέχεια γίνεται ανάθεση των τιμών των πεδίων του //r1// στα πεδία του //r2// (πεδίο προς πεδίο).
 +
 +**Σημείωση:** Κάποιες νεότερες εκδόσεις του μεταγλωττιστή εισάγουν την κλήση του κατασκευαστή αντιγραφέα και σε αυτή την περίπτωση με σκοπό τη βελτίωση της επίδοσης.
 +</WRAP>
 +
 +===== Μια πιο σύνθετη περίπτωση =====
 +
 +Στις περιπτώσεις που υπάρχουν πεδία που είναι δείκτες και δείχνουν σε άλλα αντικείμενα (στατικά ή δυναμικά δεσμευμένα) εάν δεν ορίσετε τον δικό σας κατασκευαστή αντιγραφέα, ο //default// αντιγράφει τις διευθύνσεις αυτές πεδίο προς πεδίο, όπως θα αντιγράφονταν οποιοδήποτε άλλο πεδίο. Αυτό πρακτικά σημαίνει ότι δύο ή περισσότερα αντικείμενα δείχνουν σε μία κοινή περιοχή μνήμης μέσω των αντίστοιχων πεδίων τους. Το παραπάνω μπορεί να προκαλέσει δυσλειτουργίες, καθώς η μεταβολή του περιεχομένου της κοινής μνήμης επηρεάζει το σύνολο των αντικειμένων που το μοιράζονται. Ενδεικτικό είναι το παρακάτω παράδειγμα.
 +
 +Στο παρακάτω παράδειγμα ορίζουμε την κλάση //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() const { return x; }
 +    int getY() const { 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();
 +    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{5,5};
 +  Rectangle r1{5,6,p};  
 +  Rectangle r2 = r1;
 +  Rectangle r3(r1);
 +  
 +  moveOrigin(r1, 1,-1);
 +}
 +</code>
 +
 +Ο παραπάνω κώδικας δεν περιέχει κατασκευαστή αντιγραφέα για την κλάση Rectangle. Ο //default// που δημιουργεί ο //compiler//, αντιγράφει στο αντικείμενο //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(const 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