【问题标题】:C++ copy constructor - small but important differenceC++ 复制构造函数 - 小但重要的区别
【发布时间】:2011-04-11 13:17:48
【问题描述】:

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

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

    #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 的复制构造函数。 * 这里你应该注意,复制构造函数不会复制 _vptr ,它是 Circle 中的一个不可见字段。它只复制 a 的值并返回 (*this)。这就是它不打印 Circle 的真正原因。

我还有一个问题。 当运行 test1 我得到这个输出: 默认形状构造函数 默认圆构造函数 形状复制构造函数 圈子析构函数 形状分解器 打印形状数量为 8 形状破坏子

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

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

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

【问题讨论】:

  • ***对不起,我的意思是复制构造函数。
  • 想想为什么赋值运算符和复制构造函数之间的区别很重要。现在行为应该很明显了——对新对象的第一次调用是它的构造函数,正如你所期望的那样。 Circle() 创建的临时对象与const Shape&amp; 类型的参数兼容,因为向上转换是一种隐式转换,因此复制构造函数是最佳匹配。

标签: c++ copy-constructor vtable


【解决方案1】:

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

请参阅Thinking in C++ 了解背景资料

【讨论】:

    【解决方案2】:
    Shape shape = Circle();
    

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

    Shape* shape = new Circle();
    

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

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

    【讨论】:

    • 但请注意,此处发生的复制构造特定于特定的代码示例。调用复制构造函数的唯一原因是因为它是实际参数的最佳匹配,可以很容易地有一个转换构造函数Shape(const class Circle&amp;) 会被调用。
    • 嗨,我阅读了上面分享的链接,它说复制构造函数使用 Shape 类 vptr 初始化 vptr。如果我可以改变它,并复制 Circle 的 vptr,会打印什么?
    • @Eyal:你在考虑这个问题:你不需要担心 vptr。这不是你所质疑的行为的原因,而是一种结果。如果你想调用Circle::draw(),你需要一个Circle 对象。第一个示例中的shape 不是Circle 对象,而是Shape 对象。要拥有Circle 对象,您需要将其声明为Circle(例如Circle shape;)或使用引用(例如const Shape&amp; shape = Circle();)或像在第二个示例中那样使用指针。
    【解决方案3】:

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

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

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

    【讨论】:

    • 向合唱团讲道......恐怕花时间写下这个冗长的“对象切片”的烟雾和镜子版本的人不会对我们的正确观察印象深刻关于标准和实施细节:)
    【解决方案4】:

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

    既然您已经使用 pointers 来实现运行时多态性,现在让我们尝试使用 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()

    【讨论】:

      猜你喜欢
      • 2013-10-04
      • 2015-02-24
      • 2015-06-05
      • 2013-09-09
      • 2015-07-15
      • 1970-01-01
      • 2018-10-17
      • 2018-08-27
      • 1970-01-01
      相关资源
      最近更新 更多