Конструктор копирования C ++ - небольшое, но важное отличие - PullRequest
4 голосов
/ 11 апреля 2011

Я не мог понять, что здесь происходит, подумал, что это очень странно, и после того, как я понял причину, я подумал, что поделиться ответом было бы полезно для кого-то.

Итак, учитывая этот простой код:

    #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, которое является невидимым полем в Circle.Он только копирует значение a и возвращает (* this).Это реальная причина, по которой он не печатает Circle.

Здесь у меня есть еще один вопрос.Когда я запустил test1, я получил такой вывод: Конструктор Shape по умолчанию Конструктор Circle по умолчанию Конструктор Copy Shape Конструктор Circle Distructor Shape Distructor Print Shape число 8Shape (const Shape & s) , в соответствии с этим выводом, существует вызов конструктора копирования перед фактическим созданием shape как Shape . Как это может произойти?

Test2: 1. В куче создается новый экземпляр класса Circle.(Строка new Circle выполняется) 2. Указатель на этот адрес в памяти в куче возвращается и помещается в указатель shape. В первых четырех байтах этого адреса лежитуказатель на виртуальную таблицу Circle.Вот почему test1 отличается от test2.

Важно понимать, что разница между тестами не имеет ничего общего с тем фактом, что test1 создает круг в стеке, а test2 создает круг в куче.,Ну, на самом деле это как-то связано с этим.Но настоящая причина в том, что конструктор копирования не копирует _vptr.

Ответы [ 4 ]

7 голосов
/ 11 апреля 2011

Это называется «разрезанием» класса путем копирования (неполиморфно) в базовый тип

См. Мышление в C ++ для фона

6 голосов
/ 11 апреля 2011
Shape shape = Circle();

Здесь нет назначения, и, следовательно, нет вызова оператору назначения.= используется здесь инициализация .Временный Circle объект создается Circle(), тогда его Shape часть этого временного объекта копируется в shape.Затем временный объект уничтожается, поскольку он больше не нужен.

Shape* shape = new Circle();

Объект Circle создается динамически (в куче) и возвращается указатель на этот объект.Указатель shape указывает на часть Shape этого объекта Circle.«VTPR не копируется» не является причиной разницы, это эффект.Вы написали два теста, которые делают две совершенно разные вещи, так что вы получите совершенно разные результаты.«Другой vptr» - это просто один другой результат.

Вам почти никогда не нужно беспокоиться о деталях низкоуровневой реализации, таких как «vptr» и связанных с ними вещах, при программировании на C ++.Должна быть возможность рассуждать о коде на уровне языка и интересоваться деталями реализации только при исследовании производительности и при отладке самых уродливых проблем.

0 голосов
/ 11 апреля 2011

Поскольку другие уже указали на проблему с вашим кодом, почему вы не получите одинаковый результат для обеих тестовых функций, у меня есть еще кое-что, с чем вы можете поэкспериментировать, чтобы лучше понять, как работает виртуальный механизм. работы.

Поскольку вы уже использовали указатели для достижения полиморфизма во время выполнения, давайте поэкспериментируем с ссылками сейчас. Смотри мою модификацию 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().

0 голосов
/ 11 апреля 2011

Я не знаю, почему вы думаете, что operator= вызывается до того, как shape создается - на самом деле operator= никогда не вызывается.

vptr нигде нетв стандарте C ++.Реальная причина, по которой виртуальные члены, вызывающие Shape shape, ведут себя так, как будто shape является Shape, а не Circle, состоит в том, что shape на самом деле не является Circle и никогда не было.Стандарт C ++ требует, чтобы это было так.У Shape shape нет членов Circle, для элементов данных Circle не выделено место, и было бы довольно безумно пытаться использовать виртуальные функции, когда их данные не существуют.

Shape shape создает экземпляр Shape, независимо от того, как он инициализируется.

...