C ++拷贝构造函数 - 小但重要的区别

时间:2011-04-11 13:17:49

标签: c++ copy-constructor vtable

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

所以给出了这个简单的代码:

    #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的复制构造函数。 * 在这里您应该注意,复制构造函数不会复制作为Circle中不可见字段的_vptr。它只复制a的值并返回(* this)。这就是它不打印Circle的真正原因。
  2. 我在这里有另一个问题。 运行test1时我得到了这个输出: 默认形状构造函数 默认Circle构造函数 形状复制构造函数 圆形破坏者 形状破坏者 打印形状数量为8 形状析构

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

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

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

4 个答案:

答案 0 :(得分:7)

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

有关后台处理的信息,请参阅Thinking in C++

答案 1 :(得分:6)

Shape shape = Circle();

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

Shape* shape = new Circle();

动态创建Circle对象(在堆上),并返回指向该对象的指针。 shape指针指向此Shape对象的Circle部分。 “vptr没有被复制”不是造成差异的原因,这是一种影响。你已经编写了两个测试,它们做了两件完全不同的事情,所以你得到了完全不同的结果。 “不同的vptr”只是一个不同的结果。

在使用C ++进行编程时,您几乎不需要担心像“vptr”这样的低级实现细节以及相关的事情。应该可以在语言级别推断代码,并且在调查性能和调试最棘手的问题时只关注实现细节。

答案 2 :(得分:0)

我不知道为什么你认为在构造operator=之前调用shape - 事实上operator=永远不会被调用。

C ++标准中的任何地方都没有 vptr 。虚拟成员呼叫Shape shape的行为就像shape Shape而不是Circle一样,shape真的不是{{1}从来没有。 C ++标准要求它是这样的。 Circle没有Shape shape的任何成员,没有为Circle的数据成员分配空间,尝试在数据时使用虚函数会相当疯狂不存在。

无论如何初始化,

Circle都会创建Shape shape的实例。

答案 3 :(得分:0)

正如其他人已经指出你的代码存在问题一样,为什么你不会为这两个测试函数得到相同的结果,我还有别的东西可以说你可以试验一下,以便更好地理解虚拟机制的工作原理。

由于您已经使用指针来实现运行时多态性,现在让我们尝试使用 references 。请参阅我的修改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()