Насколько это виртуально? - PullRequest
       15

Насколько это виртуально?

4 голосов
/ 26 августа 2009

Вы можете объяснить мне, почему:

int main (int argc, char * const argv[]) {
    Parent* p = new Child();
    p->Method();
    return 0;
}

печатает " Child :: Method () ", и это:

int main (int argc, char * const argv[]) {
    Parent p = *(new Child());
    p.Method();
    return 0;
}

печатает " Parent :: Method () "?

Классы:

class Parent {
public:
    void virtual Method() {
        std::cout << "Parent::Method()";
    }
};

class Child : public Parent {
public:
    void Method() {
        std::cout << "Child::Method()";
    }
};

Спасибо, Etam.

Ответы [ 10 ]

11 голосов
/ 26 августа 2009

Ваш второй код копирует a Child объект в переменную Parent. В процессе, называемом slicing , он теряет всю информацию, относящуюся к Child (т.е. все частные поля, частичные к Child), и, как следствие, всю информацию о виртуальном методе, связанную с ним.

Кроме того, оба ваших кода пропускают память (но я думаю, вы это знаете).

Вы можете использовать ссылки, хотя. E.g.:

Child c;
Parent& p = c;
p.Method(); // Prints "Child::Method"
9 голосов
/ 26 августа 2009

В первом случае вы называете фактический объект дочернего класса:

Parent* p = new Child(); // you new'ed Child class
p->Method(); // and a method of a Child class object is getting called

именно поэтому вызывается метод Child :: Method (). Во втором случае вы копируете объект класса Child в объект класса Parent:

Parent p = *(new Child()); // you new'ed Child, then allocated a separate Parent object on stack and copied onto it
p.Method(); // now you have a Parent object and its method is called

и вызывается Parent :: Method ().

3 голосов
/ 26 августа 2009

Что произойдет, если вы сделаете это?

int main (int argc, char * const argv[]) {
    Parent &p = *(new Child());
    p.Method();
    return 0;
}

Это имеет тот же "синтаксический" эффект (p не требует разыменования для его использования), но семантический эффект теперь совершенно другой, потому что вы больше не копируете часть Child в Parent.

3 голосов
/ 26 августа 2009

Виртуальное поведение доступно, только если виртуальная функция вызывается через указатель или ссылку. Когда вы говорите:

Parent p = *(new Child());
p.Method();

у вас есть фактический объект Parent, поэтому метод Parent всегда будет вызываться независимо от того, что вы назначаете для p.

2 голосов
/ 26 августа 2009

Ваш первый случай прост. Экземпляр Child создается и присваивается p. Поэтому вызов p-> Method () вызывает метод Child :: Method ().

Во втором случае происходят четыре вещи:

  1. Создается экземпляр класса Child, идентифицируемый временной переменной, назначенной компилятором.
  2. Создается экземпляр класса Parent, идентифицируемый переменной p.
  3. Конструктор копирования Parent :: Parent (Parent &) вызывается, когда создается экземпляр p, чтобы скопировать родительский «фрагмент» состояния экземпляра Child в p. Обратите внимание: если вы не определите этот конструктор копирования, компилятор создаст его для вас .
  4. Вы вызываете Method () для p, который является экземпляром Parent .

Попробуйте явно определить конструктор копирования, и вы увидите, что он вызывается.

Возможно, вы путаетесь, потому что назначения (=) в двух примерах делают разные вещи. В первом примере это просто установка одного указателя равным другому, и существует только один объект. Во втором нет указателей, поэтому вы присваиваете (нарезку) значение . Это вызывает конструктор копирования, и вы получаете два объекта (дочерний и родительский). Тот факт, что компилятор создает невидимую временную переменную, не помогает понять, что происходит. Вы можете проверить эту статью о конструкторах копирования.

Это простая ошибка, если вы привыкли к языкам, таким как Java или C #, где в основном все является ссылкой (или указателем).

Как уже говорили другие, полиморфизм работает только с указателями и ссылками, и они не используются во втором примере.

2 голосов
/ 26 августа 2009

Во втором примере происходит нарезка: дочерний экземпляр преобразуется в родительский экземпляр, у которого нет записи vtable для дочернего метода. Метод Child "отрезан".

1 голос
/ 26 августа 2009

Полиморфизм в C ++ возможен только с указателями. Различие между статическим и динамическим типом (в вашем первом примере p имеет статический тип Parent *, но имеет динамический тип Child *, и это позволяет C ++ вызывать метод производного класса) не вступает в игру с не указателями .

1 голос
/ 26 августа 2009

В первом случае у вас есть указатель «Parent» на объект «Child», который является каким-то общим, поскольку «Child» наследуется от «Parent». Следовательно, перегруженный метод 'Child' вызывается.

Во втором случае вы делаете неявное приведение экземпляра 'Child' к типу 'Parent', поэтому вы вызываете метод для объекта 'Parent'.

Надеюсь, это поможет.

0 голосов
/ 26 августа 2009

В первом случае тип объекта, на который указывает указатель p, относится к типу 'Child'. Поскольку child переопределяет виртуальный метод, определенный в базовом классе, вызывается метод child. Во втором случае вы скопировали дочерний объект в родительский объект. Здесь происходит разрезание объекта, и тип получающегося объекта имеет тип 'Parent'. Следовательно, когда вы вызываете метод, вызывается родительский метод.

0 голосов
/ 26 августа 2009

В первом примере вы вызываете метод через указатель:

p->Method();

В этом случае 'p' является указателем на Parent, и C ++ знает, что у Parent есть v-таблица, поэтому он использует ее для поиска фактического метода для вызова, который в данном случае, как вы утверждаете, равен Child::Method() потому что, когда C ++ находит v-таблицу, он находит v-таблицу «нового» дочернего экземпляра.

Во втором примере вы вызываете метод для экземпляра:

p.Method();

В этом случае 'p' является экземпляром Parent, а C ++ предполагает, что он знает, что точный тип действительно является 'Parent', и поэтому он вызывает Parent::Method(), не просматривая никаких v-таблиц.

Я только что проверил это с VS2008, и выше на самом деле то, что происходит, и нет срезов , однако, я думаю, что срез действительно происходит но вы бы увидели это только во втором случае, если бы сделали:

Parent& q=p;
q.Method();

Тогда я вижу: Parent :: Method () в процессе печати. q.Method() должен быть виртуальный вызов, но он может найти только v-таблицу для Parent.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...