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

C++ copy constructor - small but important difference

本文关键字:重要的 区别 复制 构造函数 c++      更新时间:2023-10-16

我不明白这里发生了什么,觉得很奇怪,在了解了原因之后,我认为分享答案对别人的时间是有价值的。

给出这个简单的代码:

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

嗯,因为我刚刚开始了解虚拟机制,所以我认为两个测试会产生相同的结果(打印圈)。虽然test2中是这样,但在test1中不是这样。

为了理解其中的原因,我写下了在后台真实发生的事情。

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

  1. 程序执行动作"Shape Shape ="。这实际上调用了Shape的复制构造函数。*在这里,你应该注意到复制构造函数不会复制_vptr,因为_vptr是Circle中的不可见字段。它只复制a的值并返回(*this)。这就是为什么它不打印圆圈的真正原因。

我还有一个问题。当运行test1时,我得到了这样的输出:默认形状构造函数默认的Circle构造函数形状复制构造函数圆distructor形状distructor打印形状,数字是8形状distructor

如果复制构造函数签名是Shape(const Shape&s),根据这个输出,在实际创建形状形状之前,有一个复制构造函数调用。怎么会这样?

Test2:1. 类Circle的新实例正在堆上构建。(执行new Circle行)2. 在堆内存中返回一个指向该地址的指针,并以指针的形式放置。该地址的前四个字节是指向Circle虚表的指针。这就是为什么test1不同于test2。

重要的是要理解测试之间的区别与test1在堆栈上构建一个Circle而test2在堆上构建一个Circle无关。嗯,实际上它和它有关系。但真正的原因是复制构造函数没有复制_vptr。

通过复制(非多态)到基类型来将类"切片"

参见c++中的思考

Shape shape = Circle();

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

Shape* shape = new Circle();

一个Circle对象是动态创建的(在堆上),并返回指向该对象的指针。shape指针指向Circle对象的Shape部分。"vptr未被复制"并不是造成差异的原因,而是一种结果。你写了两个测试,做两种完全不同的事情,所以你得到了完全不同的结果。"不同的vptr"仅仅是一个不同的结果。

在c++编程时,你几乎不需要担心底层实现细节,比如"vptr"和相关的东西。应该可以在语言级别上对代码进行推理,并且只在调查性能和调试最糟糕的问题时关注实现细节。

我不知道为什么你认为operator=shape被构造之前被调用——实际上operator=从来没有被调用过。

在c++标准中没有vptr。调用Shape shape的虚拟成员表现得好像shapeShape而不是Circle的真正原因是shape实际上不是Circle,而且从来都不是。c++标准要求这样做。Shape shape没有Circle的任何成员,Circle的数据成员也没有分配空间,在数据不存在的情况下尝试使用虚函数是相当疯狂的。

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

由于其他人已经指出了您的代码的问题,至于为什么您不会对两个测试函数获得相同的结果,我还有一些其他的东西要说,您可以尝试一下,以更好地理解虚拟机制是如何工作的。

既然您已经使用指针来实现运行时多态性,现在让我们尝试使用引用。看到我的修改是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()