User Tools

Site Tools


cpp:copy_constructors

This is an old revision of the document!


Κατασκευαστές Αντιγραφείς

Στην ενότητα των συναρτήσεων είδαμε την κλήση με τιμή και κλήση με αναφορά προκειμένου να περάσουμε παραμέτρους σε μία συνάρτηση. Κατά την κλήση με τιμή όταν η παράμετρος είναι ένα αντικείμενο, ένα αντίγραφο του αντικειμένου θα πρέπει να δημιουργηθεί στο stack της συνάρτησης που καλείται. Προκειμένου να γίνει αυτό η C++ ορίζει την έννοια του κατασκευαστή αντιγραφέα (copy constructor), ο οποίος δημιουργεί ένα αντικείμενο που είναι ακριβές αντίγραφο ενός άλλου αντικειμένου. Δείτε το παρακάτω παράδειγμα της μεθόδου printArea η οποία λαμβάνει ως παράμετρο ένα αντικείμενο της κλάσης Rectangle.

foo.cpp
#include <iostream>
using namespace std;
#include "Rectangle.cpp"
 
void printArea(Rectangle r) {
  cout << " area: " << r.getArea() << endl;
}
 
int main() {
  Rectangle rect(5,6);
  printArea(rect);
}

Παρακάτω δίνεται το σχηματικό διάγραμμα της στοίβας της διεργασίας πριν και κατά τη διάρκεια της κλήσης της μεθόδου printArea. Κατά την κλήση της συνάρτησης printArea, διακρίνεται η αντιγραφή του αντικειμένου rect στη μεταβλητή r που ανήκει στη στοίβα της printArea.

Ορισμός ενός κατασκευαστή αντιγραφέα

Ένας κατασκευαστής αντιγραφέας για την κλάση Rectangle ορίζεται ως εξής:

Rectangle::Rectangle(Rectangle &r) {
  width = r.width; height = r.height;
}

Συνολικά η κλάση Rectangle διαμορφώνεται ως εξής:

Rectangle.cpp
#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) {
  width = w; height = h;
}
 
Rectangle::Rectangle(int s) {
  width = s; height = s;
}
 
Rectangle::Rectangle() {
  srand(time(NULL));
  width = rand() % 10 + 1; height = rand() % 10 + 1;
}
 
// copy constructor
Rectangle::Rectangle(Rectangle &r) {
  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; }

Εάν δεν ορίσετε ένα δικό σας κατασκευαστή αντιγραφέα ο compiler δημιουργεί έναν από μόνος του αντιγράφοντας τα περιεχόμενα του αρχικού αντικειμένου στο νέο πεδίο προς πεδίο.

Μία άλλη περίπτωση κατά την οποία θα κληθεί o κατασκευαστής αντιγραφέας είναι η παρακάτω. Εδώ μαζί με την δήλωση της μεταβλητής καλείται ο κατασκευαστής αντιγραφέας του r1 με όρισμα το r2.

CopyRectangle.cpp
#include "Rectangle.cpp"
 
int main() {
  Rectangle r1(5,6);
  Rectangle r2 = r1;
}

Το παραπάνω είναι λειτουργικά ισοδύναμο με το παρακάτω.

CopyRectangle.cpp
#include "Rectangle.cpp"
 
int main() {
  Rectangle r1(5,6);
  Rectangle r2;
  r2 = r1;
}

Για τον μεταγλωττιστή όμως οι κώδικες είναι διαφορετικοί. Στην πρώτη περίπτωση καλείται ο κατασκευαστής αντιγραφέας (copy constructor), ενώ στη 2η περίπτωση καλείται ο default κατασκευαστής και στη συνέχεια γίνεται ανάθεση των τιμών των πεδίων του r1 στα πεδία του r2.

Δημιουργία κατασκευαστών αντιγραφέων

Στις περιπτώσεις που υπάρχουν πεδία δείκτες που δείχνουν σε άλλα αντικείμενα (στατικά ή δυναμικά δεσμευμένα) αντιγράφονται οι διευθύνσεις αυτές, όπως θα αντιγράφονταν οποιοδήποτε άλλο πεδίο. Αυτό πρακτικά σημαίνει ότι δύο ή περισσότερα αντικείμενα δείχνουν σε μία κοινή περιοχή μνήμης. Το παραπάνω μπορεί να προκαλέσει δυσλειτουργίες καθώς η μεταβολή του κοινού αντικειμένου επηρεάζει το σύνολο των αντικειμένων που το μοιράζονται.

Στο παρακάτω παράδειγμα ορίζουμε την κλάση Point η οποία αντιπροσωπεύει ένα σημείο στο διδιάστατο χώρο.

Point.cpp
#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; }
};

Η κλάση Rectangle που ακολουθεί ορίζει ένα πεδίο δείκτη σε αντικείμενα τύπου Point. Η δημιουργία ενός αντικειμένου τύπου Rectangle συνεπάγεται τη δυναμική δέσμευση μνήμης για το αντικείμενο τύπου Point που αυτή περιέχει. Δείτε το παράδειγμα που ακολουθεί.

Rectangle.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
#include "Point.cpp"
 
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) { origin = p; }
    Point *getOrigin() { return origin; }
};
 
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);
  }
}
 
Rectangle::Rectangle(int s, Point p) {
  width = s; height = s;
  origin = new (nothrow) Point( p.getX(), p.getY() );
  if(origin == NULL) {
    cerr << "Memory allocation failure!\n";
    exit(-1);
  }
}
 
Rectangle::Rectangle() {
  srand(time(NULL));
  width = rand() % 10 + 1; height = rand() % 10 + 1;
  origin = new (nothrow) Point{};
  if(origin == NULL) {
    cerr << "Memory allocation failure!\n";
    exit(-1);
  }
}
 
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; }
MoveOrigin.cpp
#include "Rectangle.cpp"
 
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);
}

Ο παραπάνω κώδικας αντιγράφει στο αντικείμενο r2 τα πεδία του αντικειμένου r1 πεδίο προς πεδίο. Αυτό σημαίνει ότι τα αντικείμενα r1 και r2 μοιράζονται το ίδιο αντικείμενο τύπου Point. Εάν μεταβληθούν οι συντεταγμένες του Point από το αντικείμενο r1, η μεταβολή θα ισχύει και για το αντικείμενο r2. Εάν θέλετε να αποφύγετε αυτή τη συμπεριφορά θα πρέπει να ορίσετε τον δικό σας κατασκευαστή αντιγραφέα που κάνει τα εξής:

  1. Δημιουργεί ένα νέο αντικείμενο τύπου Point.
  2. Αντιγράφει τα περιεχόμενα του παλιού στο νέο.

Ο προτεινόμενος κατασκευαστής αντιγραφέας δίνεται παρακάτω:

Rectangle::Rectangle(Rectangle &r) {
  width = r.width;
  height = r.height;
  origin = new (nothrow) Point(r.getOrigin()->getX(), r.getOrigin()->getY());
  if(origin == NULL) {
    cerr << "Memory allocation failure!";
    exit(-1);
  }
}
cpp/copy_constructors.1494396854.txt.gz · Last modified: 2017/05/10 05:14 (external edit)