C++复制构造函数 - 小但重要的区别

发布于 2024-10-31 07:50:02 字数 2510 浏览 1 评论 0原文

我无法弄清楚这里发生了什么,认为这很奇怪,在了解原因后,我认为分享答案对某人的时间很有价值。

因此,给出这个简单的代码:

    #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(*(s.a));
    }
    Shape& operator=(const Shape& s){
        cout<<"Shape operator="<<endl;

        if (&s == (this))
            return (*this);
//      this.clear();
        a = new int(*(s.a));

        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;
    }
};

为什么下面的两个测试给出了两个不同的答案:

static void test1(){
    Shape shape = Circle() ;
    shape.draw();
}

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

嗯,因为我刚刚开始了解虚拟机制,所以我认为两个测试都会产生相同的结果(打印 Circle)。虽然这是 test2 中发生的情况,但 test1 中的情况并非如此。

为了理解原因,我写了在后台实际发生的情况。

测试1: 1. 程序执行“Circle()”行。 1.1 调用 Shape 的默认构造函数(因为 Circle 是从 Shape 派生的)。 1.2 调用Circle的默认构造函数。

  1. 该程序执行操作“Shape shape=”。这实际上调用了 Shape 的复制构造函数。 * 这里您应该注意,复制构造函数不会复制 Circle 中不可见字段 _vptr。它仅复制 a 的值并返回 (*this)。这就是它不打印 Circle 的真正原因。

在这里我还有一个问题。 当运行 test1 时,我得到以下输出: 默认形状构造函数 默认圆构造函数 形状复制构造函数 圆破坏子 形状破坏器 打印形状数量为8 Shape distructor

如果复制构造函数签名是 Shape(const Shape& s),根据此输出,在实际创建 shape 之前会调用复制构造函数作为形状这怎么会发生?

测试2: 1. 类 Circle 的一个新实例正在堆上构建。 (执行new Circle行) 2. 返回指向堆上内存中该地址的指针,并将其放置在指针形状中。该地址的前四个字节中包含指向 Circle 虚拟表的指针。这就是 test1 与 test2 不同的原因。

重要的是要理解,测试之间的差异与 test1 在堆栈上构建 Circle 和 test2 在堆上构建 Circle 的事实无关。嗯,其实这也是有一定关系的。但真正的原因是复制构造函数没有复制_vptr。

I couldn't figure out what is happening here, thought it is very strange, and after getting to understand the reason I thought sharing the answer would be valuable to somebody's time.

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(*(s.a));
    }
    Shape& operator=(const Shape& s){
        cout<<"Shape operator="<<endl;

        if (&s == (this))
            return (*this);
//      this.clear();
        a = new int(*(s.a));

        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 is the two following tests gives two different answers:

static void test1(){
    Shape shape = Circle() ;
    shape.draw();
}

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

Well, because I'm getting to know the virtual mechanism just now, I figured that both test will produce the same result (printing Circle). While this is what happens in test2 it is not the case in test1.

To understand why, I wrote what really happens in the backround.

Test1:
1. The program perform the line " Circle() ".
1.1 a call to the default constructor of Shape is made (because Circle is derived from Shape).
1.2 a call to the default constructor of Circle is made.

  1. The program performs the action " Shape shape = ". This actually calls to the copy constructor of Shape. * here you should note that the copy constructor does not copy the _vptr that is an invisible field in Circle. It only copies the value of a and returns (*this). This is the real reason why it doesn't print Circle.

Here I do have another question.
When ran test1 I got this output:
Default Shape constructor
Default Circle constructor
Shape copy constructor
Circle distructor
Shape distructor
Print Shape the number is 8
Shape distructor

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

Test2:
1. A new instance of class Circle is being build on the heap. (The line new Circle is performed)
2. A pointer to that address in memory on the heap is returned and placed in the pointer shape. In the first four bytes of this address lies the pointer to the virtual table of Circle. That is why test1 is different from test2.

It is important to understand that the the difference between the test has nothing to do with the fact that test1 builds a Circle on the stack and test2 builds 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 the _vptr.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

同尘 2024-11-07 07:50:02

这称为通过复制(非多态性)到基类型来“切片”类

请参阅 用 C++ 思考作为背景资料

It's called 'slicing' the class by copying (non-polymorphically) to the base type

See Thinking in C++ for backgrounder

小忆控 2024-11-07 07:50:02
Shape shape = Circle();

这里没有赋值,因此没有调用赋值运算符。这里使用的=初始化。由 Circle() 创建临时 Circle 对象,然后将该临时对象的 Shape 部分复制构造为 shape >。然后,临时对象将被销毁,因为不再需要它。

Shape* shape = new Circle();

动态创建一个Circle对象(在堆上)并返回指向该对象的指针。 shape 指针指向此 Circle 对象的 Shape 部分。 “vptr 没有被复制”不是差异的原因,而是结果。您已经编写了两个测试来执行两个完全不同的事情,因此您会得到完全不同的结果。 “不同的vptr”只是一种不同的结果。

在使用 C++ 编程时,您几乎不需要担心低级实现细节,例如“vptr”和相关事物。应该可以在语言级别推理代码,并且在调查性能和调试最丑陋的问题时只关心实现细节。

Shape shape = Circle();

There is no assignment here and thus there is no call to the assignment operator. The = used here is initialization. A temporary Circle object is created by Circle() then its Shape part of that temporary object is copy constructed into shape. The temporary object is then destroyed because it is no longer needed.

Shape* shape = new Circle();

A Circle object is created dynamically (on the heap) and a pointer to that object is returned. The shape pointer points to the Shape part of this Circle object. "The vptr not getting copied" is not the cause of the difference, it's an effect. You've written two tests that do two completely different things so you get to completely different results. The "different vptr" is merely one result that is different.

You should almost never need to worry about low-level implementation details like "the vptr" and related things when programming in C++. It should be possible to reason about the code at the language level, and only concern yourself with implementation details when investigating performance and when debugging the ugliest of issues.

无尽的现实 2024-11-07 07:50:02

我不知道为什么您认为在构造 shape 之前调用 operator= ——事实上 operator= 从未被调用。

C++ 标准中的任何地方都没有vptr。调用 Shape shape 的虚拟成员表现得好像 shapeShape 而不是 Circle 的真正原因是这个shape确实不是一个Circle,而且从来都不是。 C++ 标准要求它是这样的。 Shape shape 没有任何 Circle 的成员,没有为 Circle 的数据成员分配空间,这将是相当疯狂的当数据不存在时尝试使用虚拟函数。

Shape shape 创建一个 Shape 实例,无论它是如何初始化的。

I don't know why you think operator= is called before shape is constructed -- in fact operator= is never called.

There is no vptr anywhere in the C++ standard. The real reason that virtual members called on Shape shape act as if shape is a Shape and not a Circle is that shape really is not a Circle and never was. The C++ standard requires it to be this way. Shape shape doesn't have any members of Circle, there is no space allocated for the data members of Circle, and it would be rather crazy to try to use the virtual functions when their data doesn't exist.

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

愁以何悠 2024-11-07 07:50:02

正如其他人已经指出的那样,您的代码存在问题,至于为什么两个测试函数不会得到相同的结果,我还有其他话要说,您可以进行实验,以便更好地理解虚拟机制的工作原理。

由于您已经使用指针来实现运行时多态性,现在让我们尝试一下引用。看看我的修改是 test1()

static void test1(){
    Circle circle;
    Shape & shape = circle;  //note &
    shape.draw();
}

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

现在这两个函数都会打印相同的内容。

底线是:在C++中,运行时多态性只能通过指针引用来实现,其静态类型是基类,动态类型是它指向/引用的对象。

让我们做更多实验:

  Circle circle;
  circle.draw();

  Shape & s1 = circle;
  s1.draw();

  Shape & s2 = s1;
  s2.draw();

  Shape & s3 = s2;
  s3.draw();

s2.draw()s3.draw() 会做什么?答案是:它们会做与 s1.draw()circle.draw() 相同的事情。意味着,它们都会调用Circle::draw(),没有一个会调用Shape::draw()

As others already have pointed out the problem with your code, as to why you wouldn't get same result for both test functions, I've something else to say which you can experiment with to have better understanding of how virtual mechanism works.

Since you already have used pointers to achieve runtime polymorphism, lets experiment with references now. See my modification is 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 these function would print same thing.

The bottomline is : in C++, runtime polymorphism is achieve only through pointers and references whose static type is base class, and dynamic type is the object that it points/refers to.

Lets do more experiment:

  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 would do the same thing as s1.draw() and circle.draw() would do. Means, all of them would call Circle::draw(), none would call Shape::draw().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文