4

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

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

    #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 中并非如此。

为了理解原因,我写了幕后真正发生的事情。

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

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

在这里,我确实有另一个问题。当运行 test1 我得到这个输出: Default Shape constructor Default Circle constructor Shape copy constructor Circle disstructor Shape distructor Print Shape the number is 8 Shape distructor

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

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

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

4

4 回答 4

8

它被称为通过复制(非多态)到基类型来对类进行“切片”

有关背景资料,请参阅Thinking in C++

于 2011-04-11T13:21:20.733 回答
6
Shape shape = Circle();

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

Shape* shape = new Circle();

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

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

于 2011-04-11T13:21:54.870 回答
0

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

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

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

于 2011-04-11T13:21:26.663 回答
0

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

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

于 2011-04-11T13:27:12.250 回答