Я пытаюсь понять следующий фрагмент кода:
#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, который и называется.
Две функции имеют разные подписи, поэтому 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)
Простой способ думать о сокрытии заключается в следующем — посмотрите на строку d-> f (3.14F); из примера:
Поскольку типы аргументов этих двух функций различаются, один в 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 действительно может быть преобразован в целочисленный тип.