Путаница с виртуальными функциями и производными классами

Я пытаюсь понять следующий фрагмент кода:

#include<iostream>
using namespace std;
class Base {
public:
virtual void f(float) { cout << "Base::f(float)\n"; }
};
class Derived : public Base {
public:
virtual void f(int) { cout << "Derived::f(int)\n"; }
};
int main() {
Derived *d = new Derived();
Base *b = d;
d->f(3.14F);
b->f(3.14F);
}

Это печатает

Derived::f(int)
Base::f(float)

И я не уверен, почему именно.

Первый вызов d-> f (3.14F) вызывает функцию f из Derived. Я не уверен на 100%, почему. Я посмотрел на это (http://en.cppreference.com/w/cpp/language/implicit_cast), которое говорит:

Значение типа с плавающей запятой может быть преобразовано в значение любого целого типа. Дробная часть усекается, то есть дробная часть отбрасывается. Если значение не может вписаться в тип назначения, поведение не определено

Что для меня говорит, что вы не можете сделать это, так как float не вписывается в int. Почему это неявное преобразование разрешено?

Во-вторых, даже если я просто приму вышеизложенное как нормальное, второй вызов b-> f (3.14F) не имеет смысла. b-> f (3.14F) вызывает виртуальную функцию f, поэтому она динамически разрешается для вызова функции f (), связанной с динамическим типом объекта, на который указывает b, который является производным объектом. Поскольку нам разрешено конвертировать 3.14F в int, так как первый вызов функции указывает, что это допустимо, это (на мой взгляд) должно снова вызвать функцию Derived :: f (int). Тем не менее он вызывает функцию в базовом классе. Так почему это?

редактировать: вот как я понял это и объяснил себе.

b является указателем на базовый объект, поэтому мы можем использовать b только для доступа к членам базового объекта, даже если b действительно указывает на некоторый производный объект (это стандартная OO / наследование).

Единственное исключение из этого правила — когда функция-член Base объявлена ​​как виртуальная. В таком случае производный объект может переопределение эта функция, предоставляя другую реализацию с помощью точно такая же подпись. Если это происходит, тогда эта производная реализация будет вызываться во время выполнения, даже если мы получим доступ к функции-члену через указатель на базовый объект.

Теперь, во фрагменте кода выше, у нас нет переопределения, потому что сигнатуры B :: f и D :: f различны (одна — float, другая — int). Поэтому, когда мы вызываем b-> f (3.14F), единственной рассматриваемой функцией является исходный B :: f, который и называется.

4

Решение

Две функции имеют разные подписи, поэтому f в derived не переопределяет виртуальную функцию в base, Просто потому что типы int а также float может быть неявно приведен, не имеет эффекта здесь.

virtual void f(float) { cout << "Base::f(float)\n"; }
virtual void f(int) { cout << "Derived::f(int)\n"; }

Ключ к тому, что происходит, можно увидеть с новым override ключевое слово в C ++ 11, это очень эффективно для устранения подобных ошибок.

virtual void f(int) override { cout << "Derived::f(int)\n"; }

из которого gcc выдает ошибку:

виртуальная пустота Derived :: f (int) ’помечена как переопределенная, но не переопределяет

лязг

ошибка: «f» помечен как «переопределить», но не переопределяет функции-члены

http://en.cppreference.com/w/cpp/language/override

РЕДАКТИРОВАТЬ:

для вашего второго пункта, вы можете выставить float перегрузка от base в derived который предоставляет неявно совместимую функцию-член. вот так:

class Derived : public Base {
public:
using Base::f;
virtual void f(int) { cout << "Derived::f(int)\n"; }
};

Теперь передавая поплавок функции-члену f связывает ближе к функции, определенной в базе и производит:

Base::f(float)
Base::f(float)
11

Другие решения

Простой способ думать о сокрытии заключается в следующем — посмотрите на строку d-> f (3.14F); из примера:

  1. Первый шаг для компилятора — выбрать имя класса. Имя функции-члена f используется для этого. Типы параметров не используются. Производное выбрано.
  2. Следующим шагом для компилятора является выбор функции-члена из этого класса. Типы параметров используются. void Derived :: f (int); является единственной подходящей функцией с правильным именем и параметрами из класса Derived.
  3. Происходит сужение преобразования типов из типа float в int.
2

Поскольку типы аргументов этих двух функций различаются, один в Derived класс на самом деле не перекрывает тот из Base, Вместо Derived::f шкуры Base::f (сейчас у меня нет стандарта, поэтому я не могу процитировать эту главу).

Это означает, что когда вы звоните d->f(3.14f)компилятор даже не учитывает B::f, Это решает вызов D::f, Тем не менее, когда вы звоните b->f(3.14f)единственная версия, которую может выбрать компилятор B::f как D::f не отменяет это.

Ваше чтение If the value can not fit into the destination type, the behavior is undefined неправильно. Это говорит значение не тип. Так что значение 3.0f вписывается в int, а 3e11 — нет. В последнем случае поведение не определено. Первая часть вашей цитаты, A prvalue of floating-point type can be converted to prvalue of any integer type. объясняет почему d->f(3.14f) решено D::f(int) — float действительно может быть преобразован в целочисленный тип.

1
По вопросам рекламы [email protected]