C ++ copy constructor - a small but important difference

I could not understand what was happening here, I thought it was very strange, and, understanding the reason, I thought that a joint answer would be valuable to someone.

So, given this simple code:

#include <iostream> using namespace std; class Shape { public: int* a; Shape(){ cout<<"Default Shape constructor"<<endl; a = new int(8); // default } Shape(int n){ a = new int(n); cout<<"Shape(n) constructor"<<endl; } // copy constructor Shape(const Shape& s){ cout<<"Shape copy constructor"<<endl; a = new int(*(sa)); } Shape& operator=(const Shape& s){ cout<<"Shape operator="<<endl; if (&s == (this)) return (*this); // this.clear(); a = new int(*(sa)); return (*this); } virtual void draw(){ cout<<"Print Shape the number is "<<*a<<endl; }; virtual ~Shape(){ delete a; cout<<"Shape distructor"<<endl; } }; class Circle : public Shape { public: int b; Circle() { cout<<"Default Circle constructor"<<endl; b=0; } virtual void draw() { cout<<"Printing Circle. The number is "<<b<<endl; } ~Circle(){ cout<<"Circle distructor"<<endl; } }; 

Why do the following two tests give two different answers:

 static void test1(){ Shape shape = Circle() ; shape.draw(); } static void test2(){ Shape* shape = new Circle() ; shape->draw(); delete shape; } 

Good, because now I recognize the virtual mechanism, I decided that both tests will lead to the same result (Circle print). Although this is what happens in test2, it is not so in test1.

To understand why, I wrote what actually happens in the background.

Test1: 1. The program will execute the line " Circle () ". 1.1 the default constructor of the Shape is called (because the Circle is derived from Shape). 1.2 the default constructor is called Circle.

  • The program performs the action " Form = ". This actually calls the Shape copy constructor. * Here you should notice that the copy constructor does not copy _vptr, which is an invisible field in Circle. It only copies the value of a and returns (* this). This is the real reason he doesn't print Circle.

Here I have another question. When I started test1, I got this conclusion: The default Shape constructor The default loop constructor The copy constructor of the form Circle dialog Format Formatter: 8 Shape trim

If the signature of the copy constructor is Shape (const Shape & s), according to this output, there is a call to copy the constructor before creating the form in the form. How can this happen?

Test2: 1. A new instance of the Circle class is built on the heap. (The New Circle line is executed) 2. A pointer to this address in memory in the heap is returned and placed in the form of a pointer. The first four bytes of this address contain a pointer to the Circle virtual table. This is why test1 is different from test2.

It is important to understand that the difference between the test has nothing to do with the fact that test1 creates a circle on the stack, and test2 creates a circle on the heap. Well, actually it has something to do with it. But the real reason is that the copy constructor does not copy _vptr.

+4
source share
4 answers

It is called β€œslicing” a class by copying (not polymorphically) to the base type

See Thinking in C ++ for backgrounder

+6
source
 Shape shape = Circle(); 

There is no assignment and therefore no call to the assignment operator. Used here = is initialization. The Circle temporary object is created by Circle() , then its part of the Shape this temporary object is copied to Shape . Then the temporary object is destroyed because it is no longer needed.

 Shape* shape = new Circle(); 

A Circle object is created dynamically (on the heap) and a pointer to this object is returned. The Shape pointer points to the Shape part of this Circle . "Vptr, not copyable" is not the cause of the difference, it is the effect. You wrote two tests that do two completely different things, so you get completely different results. "Different vptr" is just one result that is different.

You almost don't need to worry about low-level implementation details such as "vptr" and related things when programming in C ++. It should be possible to talk about code at the language level, and only care about implementation details when researching performance and debugging the ugliest problems.

+5
source

I don’t know why you think that operator= is called before the shape is built - in fact operator= never called.

There is no vptr in the C ++ standard. The real reason virtual members called in Shape shape act as if shape is shape rather than Circle is because shape really is not Circle and never was. The C ++ standard requires this to be the case. Shape shape has no Circle members, there is no space for Circle data elements, and it would be pretty crazy to try using virtual functions when their data does not exist.

Shape shape creates an instance of shape , regardless of how it is initialized.

0
source

As already noted, the problem with your code is why you won’t get the same result for both test functions, I still need to say with what you can experiment to better understand how the virtual mechanism works.

Since you have already used pointers to achieve polymorphism at run time, let's now experiment with links. See My modification test1() :

 static void test1(){ Circle circle; Shape & shape = circle; //note & shape.draw(); } static void test2(){ Shape* shape = new Circle() ; shape->draw(); delete shape; } 

Now both of these functions will print the same thing.

Bottom line: In C ++, run-time polymorphism is achieved only with pointers and references whose static type is the base class and the dynamic type is the object it points to / refers to.

Allows you to do more experiments:

  Circle circle; circle.draw(); Shape & s1 = circle; s1.draw(); Shape & s2 = s1; s2.draw(); Shape & s3 = s2; s3.draw(); 

What would s2.draw() and s3.draw() do? The answer is: they will do the same as s1.draw() and circle.draw() . So, they will all call Circle::draw() , no one will name Shape::draw() .

0
source

Source: https://habr.com/ru/post/1347542/


All Articles