Jump to content
  • Advertisement

Archived

This topic is now archived and is closed to further replies.

theNestruo

Copy constructor

This topic is 5897 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, and thanks in advance. Well, we've got a problem and we cannot solve it, so here we are coming for a little help. We have a board, and some pieces on it. We need to make a "backup" of the board before a movement to check if it's valid or not, and, if it's not valid, turn back the board to the stored position. The simplified code is:
      
class cBoard {
  cPiece ***piece;
  cBoard (int width, int height); // (1) Constructor

  cBoard (const cBoard&): // (2) Constructor (copy-constructor)

  putPiece (int row, int column, cPiece* piece);
  ...
}

class cPiece { // Abstract class

  ...
}

class cChessPiece: public cPiece { // Abstract class

  ...
}

class cPawn: public cChessPiece { // Concrete class

   ...
}

class cKnight: public cChessPiece { // Concrete class

   ...
}
  
In the (1) constructor, we allocate a two dimensional array of pointers to pieces and, when we call to the putPiece() procedure, we pass a cPawn, cKnight, etc... as third parameter. We don't know how to make the (2) constructor (the copy-constructor) for the board: we create the array with the same size as the original, and we initialize as NULL all the piece[ i ][j] that are NULL in the original, but if the original has a piece, we cannot copy the pointer value because in the destructor of the cBoard class we delete all the pieces on it, so after this...
         
{
  cBoard backup=board;
  // Do some stuff here

}
  
...the original board has pointers to the deleted pieces! We're trying to do a copy-constructor for the pieces, but we cannot copy the internal atributes of the classes cChessPieces, cPawn, etc...; just the atributes of cPiece. We tried also to made the copy-constructor virtual, so each kind of piece will copy his own parameters, but the compiler told us: "inline is the only modifier available for constructors". Aaargh! We're going crazy! How can we copy the board? Please, help us; and thanks in advance again! theNestruo Syntax error in 2410 Ok EDIT: piece[ i ][j] turned the second half of the post as this . [edited by - theNestruo on May 29, 2002 2:42:38 PM]

Share this post


Link to post
Share on other sites
Advertisement
but if the original has a piece, we cannot copy the pointer value because in the destructor of the cBoard class we delete all the pieces on it

Sounds to me like like you are missing an overloaded assignment operator for CBoard.

CBoard& CBoard::operator = ( const CBoard& l )
{
// run through all the pieces in l an copy
// them over to this instance.

// return *this
}

I think that will do, might be wrong though

Share this post


Link to post
Share on other sites
quote:

Sounds to me like like you are missing an overloaded assignment operator for CBoard.



Thanks FrikkaSoft, but the cBoard::cBoard (const cBoard &); overloads the = operator; so we have it. What we need is how to implement it.

Any other solution, please?

theNestruo

Syntax error in 2410
Ok

Share this post


Link to post
Share on other sites
The copy constructor will have to make a deep copy of the board, ie. the new board contains a copy of the other boards data.


University is a fountain of knowledge, and students go there to drink.

Share this post


Link to post
Share on other sites
We haven''t expressed it correcly...

We know that we''ve to do a deep copy, but we cannot copy the pieces!


  
class cPiece { // Abstract class, so we cannot instantiate it

protected:
// Data that all the pieces have: row, column, color, etc...

public:
cPiece (const cPiece &); // Copy-constructor

// ...

}

class cChessPiece: public cPiece { // Also an abstract class

protected:
// Data that only the chess pieces have: value, moved, etc...

public:
cChessPiece (const cChessPiece &); // Copy-constructor

// ...

}

class cPawn: public cChessPiece { // Concrete (instantiable) class

protected:
// Additional data for pawns: can_en_passant, etc...

public:
cPawn (const cPawn &); // Copy-constructor

// ...

}

// And the same for Rooks, Knights and all others (any of them haven''t additional data)



When we duplicate the board, we do something like (more or less):


  
cBoard::cBoard (const cBoard &original) {
width=original.width;
// Etc...

piece=new cPiece** [width];
for (i=0; i<width; i++) {
piece=new cPiece* [height];
for (j=0; i<height; j++) {
if (original.piece[i][j]==NULL)
piece[i][j]=NULL;
else {
// Here is the problem:

// If we copy the pointer value, when calling the destructor of the backup board, the pieces will be deleted, but they cannot be deleted ''cause the original board points to them

// We cannot do piece[i][j]=new cPiece (*original.piece[i][j]) because the cPiece class cannot have instances

}
}
}
}

cBoard::~cBoard (void) {
for (i=0; i<width; i++) {
for (j=0; j<height; j++) {
if (piece[i][j]!=NULL)
delete piece[i][j]; // Destruct all the pieces in the board

}
delete[] piece[i];
}
delete[] piece;
}


So, when we duplicate the board:


  
{
cBoard backup=board;
// Operate with the board

} // Here, in the closing bracket, the backup board is destroyed, and the same all the pieces on it


board.piece[i][j]->DoSomething (); // This is illegal because the pieces in the board have been destroyed in the previous lines!!!



We hope we''ve explained it clearer now.

Can anyone help us before we call ~ourselves(); !?

Thanks in advance!

theNestruo

Syntax error in 2410
Ok

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Hi!
I just read your problem.

I''d have to look in a book like Thinking in C++ for the correct
solution.

I quick-hack I have in mind is to implement something like introspection:

like
class aPice...
{
const int PAWN = 1;
const int KNIGHT =2;

virtual int getType() = 0;
virtual *aPice makeCopy() =0;
};

class pawn...
{
int getType(){ return PAWN;}
*aPice makeCopy() {return new pawn(*this)}; // copy constructor used!
};


in the copy loop - you''s have to check like

switch (p->getType())
case PAWN:
{
pawn pa= (pawn *)p;
apice_copy = pa->makeCopy()
}


or something like that.

I think it might work - not anything near a clean solution...
have to check... the books..

by
Chris



Share this post


Link to post
Share on other sites
quote:

What''s this "we" crap? I have nothing to do with this


We''re two persons developing it, because two brains fails twice than one! That''s why we use "we" instead "I".

To Chris:
We cannot do it because "cBoard" it''s going to be a reusable component, so if anyone want to create a "shogui" game (deriving cShoguiBoard, cShoguiGame and cShoguiPieces from our library), it won''t be possible, because our cBoard will only duplicate the Pawns, Rooks, Knights, etc...
We had the same idea, but well... We cannot use it (even when we''re sure it works!)

Thanks anyway!



theNestruo

Syntax error in 2410
Ok

Share this post


Link to post
Share on other sites
Hi!

just for info - what do you need pieces for in the main class.

I mean - the pieces are just that - pieces -!
There is no information about what kind of pieces they once were. If you call any method of the pieces - you will call the pieces - method - none else!

This is called "upcasting" and the objects "upcasted" are sliced. That means basically they are one instances of the "upper" class.

So - why do you need to copy information of the specialized classes?

And - if you don''t need the specialized informmation - which you lose! - why not build a piece class that is not abstract - and instantiate that!


Perhaps you should even consider doing composition instead of inheritance?

Well that doesn''t solve you problem - just would like to know...

Bye

Chris


Share this post


Link to post
Share on other sites
Hi (again)!

Ok - I just had a look at "Thinking in C++" (Vol1 and Vol2).

There is no way you can do what you want!

They have a solution there - but in principle the same I gave you before - using "introspection" and a factory pattern.
(Chapter 11 of Vol2)

Better do some redesigning :-)

Chris



Here a copy of what Bruce says:

Virtual constructors

Show simpler version of virtual constructor scheme, letting the user create the object with new. Probably make constructor for objects private and use a maker function to force all objects on the heap.


One of the primary goals of using a factory is so that you can organize your code so you don’t have to select an exact type of constructor when creating an object. That is, you can say, “I don’t know precisely what type of object you are, but here’s the information: Create yourself.”


In addition, during a constructor call the virtual mechanism does not operate (early binding occurs). Sometimes this is awkward. For example, in the Shape program it seems logical that inside the constructor for a Shape object, you would want to set everything up and then draw( ) the Shape. draw( ) should be a virtual function, a message to the Shape that it should draw itself appropriately, depending on whether it is a circle, square, line, and so on. However, this doesn’t work inside the constructor, for the reasons given in Chapter XX: Virtual functions resolve to the “local” function bodies when called in constructors.


If you want to be able to call a virtual function inside the constructor and have it do the right thing, you must use a technique to simulate a virtual constructor (which is a variation of the Factory Method). This is a conundrum. Remember the idea of a virtual function is that you send a message to an object and let the object figure out the right thing to do. But a constructor builds an object. So a virtual constructor would be like saying, “I don’t know exactly what type of object you are, but build yourself anyway.” In an ordinary constructor, the compiler must know which VTABLE address to bind to the VPTR, and if it existed, a virtual constructor couldn’t do this because it doesn’t know all the type information at compile-time. It makes sense that a constructor can’t be virtual because it is the one function that absolutely must know everything about the type of the object.


And yet there are times when you want something approximating the behavior of a virtual constructor.


In the Shape example, it would be nice to hand the Shape constructor some specific information in the argument list and let the constructor create a specific type of Shape (a Circle, Square) with no further intervention. Ordinarily, you’d have to make an explicit call to the Circle, Square constructor yourself.


Coplien[29] calls his solution to this problem “envelope and letter classes.” The “envelope” class is the base class, a shell that contains a pointer to an object of the base class. The constructor for the “envelope” determines (at runtime, when the constructor is called, not at compile-time, when the type checking is normally done) what specific type to make, then creates an object of that specific type (on the heap) and assigns the object to its pointer. All the function calls are then handled by the base class through its pointer. So the base class is acting as a proxy for the derived class:


//: C11:VirtualConstructor.cpp
//{L} ../TestSuite/Test
#include <iostream>
#include <string>
#include <exception>
#include <vector>
using namespace std;

class Shape {
Shape* s;
// Prevent copy-construction & operator=
Shape(Shape&);
Shape operator=(Shape&);
protected:
Shape() { s = 0; };
public:
virtual void draw() { s->draw(); }
virtual void erase() { s->erase(); }
virtual void test() { s->test(); };
virtual ~Shape() {
cout << "~Shape\n";
if(s) {
cout << "Making virtual call: ";
s->erase(); // Virtual call
}
cout << "delete s: ";
delete s; // The polymorphic deletion
}
class BadShapeCreation : public exception {
string reason;
public:
BadShapeCreation(string type) {
reason = "Cannot create type " + type;
}
~BadShapeCreation() throw() {}
const char *what() const throw() {
return reason.c_str();
}
};
Shape(string type) throw(BadShapeCreation);
};

class Circle : public Shape {
Circle(Circle&);
Circle operator=(Circle&);
Circle() {} // Private constructor
friend class Shape;
public:
void draw() { cout << "Circle::draw\n"; }
void erase() { cout << "Circle::erase\n"; }
void test() { draw(); }
~Circle() { cout << "Circle::~Circle\n"; }
};

class Square : public Shape {
Square(Square&);
Square operator=(Square&);
Square() {}
friend class Shape;
public:
void draw() { cout << "Square::draw\n"; }
void erase() { cout << "Square::erase\n"; }
void test() { draw(); }
~Square() { cout << "Square::~Square\n"; }
};

Shape::Shape(string type)
throw(Shape::BadShapeCreation) {
if(type == "Circle")
s = new Circle;
else if(type == "Square")
s = new Square;
else throw BadShapeCreation(type);
draw(); // Virtual call in the constructor
}

char* shlist[] = { "Circle", "Square", "Square",
"Circle", "Circle", "Circle", "Square", "" };

int main() {
vector shapes;
cout << "virtual constructor calls:" << endl;
try {
for(char** cp = shlist; **cp; cp++)
shapes.push_back(new Shape(*cp));
} catch(Shape::BadShapeCreation e) {
cout << e.what() << endl;
return 1;
}
for(int i = 0; i < shapes.size(); i++) {
shapes->draw();
cout << "test\n";
shapes[i]->test();
cout << "end test\n";
shapes[i]->erase();
}
Shape c("Circle"); // Create on the stack
cout << "destructor calls:" << endl;
for(int j = 0; j < shapes.size(); j++) {
delete shapes[j];
cout << "\n------------\n";
}
} ///:~
The base class Shape contains a pointer to an object of type Shape as its only data member. When you build a “virtual constructor” scheme, you must exercise special care to ensure this pointer is always initialized to a live object.


Each time you derive a new subtype from Shape, you must go back and add the creation for that type in one place, inside the “virtual constructor” in the Shape base class. This is not too onerous a task, but the disadvantage is you now have a dependency between the Shape class and all classes derived from it (a reasonable trade-off, it seems). Also, because it is a proxy, the base-class interface is truly the only thing the user sees.


In this example, the information you must hand the virtual constructor about what type to create is very explicit: It’s a string that names the type. However, your scheme may use other information – for example, in a parser the output of the scanner may be handed to the virtual constructor, which then uses that information to determine which token to create.


The virtual constructor Shape(type) can only be declared inside the class; it cannot be defined until after all the derived classes have been declared. However, the default constructor can be defined inside class Shape, but it should be made protected so temporary Shape objects cannot be created. This default constructor is only called by the constructors of derived-class objects. You are forced to explicitly create a default constructor because the compiler will create one for you automatically only if there are no constructors defined. Because you must define Shape(type), you must also define Shape( ).


The default constructor in this scheme has at least one very important chore – it must set the value of the s pointer to zero. This sounds strange at first, but remember that the default constructor will be called as part of the construction of the actual object – in Coplien’s terms, the “letter,” not the “envelope.” However, the “letter” is derived from the “envelope,” so it also inherits the data member s. In the “envelope,” s is important because it points to the actual object, but in the “letter,” s is simply excess baggage. Even excess baggage should be initialized, however, and if s is not set to zero by the default constructor called for the “letter,” bad things happen (as you’ll see later).


The virtual constructor takes as its argument information that completely determines the type of the object. Notice, though, that this type information isn’t read and acted upon until runtime, whereas normally the compiler must know the exact type at compile-time (one other reason this system effectively imitates virtual constructors).


Inside the virtual constructor there’s a switch statement that uses the argument to construct the actual (“letter”) object, which is then assigned to the pointer inside the “envelope.” At that point, the construction of the “letter” has been completed, so any virtual calls will be properly directed.


As an example, consider the call to draw( ) inside the virtual constructor. If you trace this call (either by hand or with a debugger), you can see that it starts in the draw( ) function in the base class, Shape. This function calls draw( ) for the “envelope” s pointer to its “letter.” All types derived from Shape share the same interface, so this virtual call is properly executed, even though it seems to be in the constructor. (Actually, the constructor for the “letter” has already completed.) As long as all virtual calls in the base class simply make calls to identical virtual function through the pointer to the “letter,” the system operates properly.


To understand how it works, consider the code in main( ). To fill the vector shapes, “virtual constructor” calls are made to Shape. Ordinarily in a situation like this, you would call the constructor for the actual type, and the VPTR for that type would be installed in the object. Here, however, the VPTR used in each case is the one for Shape, not the one for the specific Circle, Square, or Triangle.


In the for loop where the draw( ) and erase( ) functions are called for each Shape, the virtual function call resolves, through the VPTR, to the corresponding type. However, this is Shape in each case. In fact, you might wonder why draw( ) and erase( ) were made virtual at all. The reason shows up in the next step: The base-class version of draw( ) makes a call, through the “letter” pointer s, to the virtual function draw( ) for the “letter.” This time the call resolves to the actual type of the object, not just the base class Shape. Thus the runtime cost of using virtual constructors is one more virtual call every time you make a virtual function call.


In order to create any function that is overridden, such as draw( ), erase( ) or test( ), you must proxy all calls to the s pointer in the base class implementation, as shown above. This is because, when the call is made, the call to the envelope’s member function will resolve as being to Shape, and not to a derived type of Shape. Only when you make the proxy call to s will the virtual behavior take place. In main( ), you can see that everything works correctly, even when calls are made inside constructors and destructors.




Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!