Confusion over virtual functions and derived classes

I am trying to understand the following bit of code:

#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); } 

Will print

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

And I don’t know exactly why.

The first call to d-> f (3.14F) calls the function f in Derived. Why am I not 100% sure. I looked at this (http://en.cppreference.com/w/cpp/language/implicit_cast) which says:

The value of a floating point class can be converted to a prvalue of any integer type. The fractional part is truncated, i.e. The fractional part is discarded. If the value cannot fit into the destination type, the behavior is undefined

Which tells me that you cannot do this, since the float does not fit into the int. Why is this implicit conversion allowed?

Secondly, even if I just accept the above as OK, the second call to b-> f (3.14F) does not make sense. b-> f (3.14F) calls the virtual function f, so it is dynamically resolved to call f () associated with the dynamic type of the object pointed to by b, which is a derived object. Since we are allowed to convert 3.14F to int, because the first call to the function indicates that it is legal, this (in my opinion) should again call the Derived :: f (int) function. However, it calls a function in the base class. So why is that?

edit: this is how I understood it and explained it to myself.

b is a pointer to a base object, so we can only use b to access the elements of the base object, even if b really points to some Derived object (this is standard OO / inheritance material).

The only exception to this rule is when a Base member function is declared virtual. In this case, the Derived object can override this function by providing a different implementation using the same signature . If this happens, then this derived implementation will be called at run time, even if we access the member function through a pointer to the underlying object.

Now, in the code snippet above, we have no redefinition, because the signatures of B :: f and D :: f are different (one is float, the other is int). Therefore, when we call b-> f (3.14F), the only function that is considered is the original B :: f, which is what is called.

+4
source share
3 answers

The two functions have different signatures, so f in derived does not override the virtual function in base . Just because int and float types can be implicitly dropped, there is no effect.

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

The key to what happens can be seen with the new override keyword in C ++ 11, which is very effective in reducing such errors.

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

from which gcc produces an error:

virtual void Derived :: f (int) override marked but not overridden

clank

error: 'f' marked "override" but does not cancel any member functions

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

EDIT:

for your second point, you can actually infer the float overload from base to derived , which provides an implicitly compatible member function. So:

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

Now the transfer of the float function to member f tied closer to the function defined in the database and creates:

 Base::f(float) Base::f(float) 
+11
source

An easy way to think about hiding is this: look at the line d-> f (3.14F); from an example:

  • The first step for the compiler is to select a class name. For this, the function name of the function f is used. No parameter types are used. A choice is made.
  • The next step for the compiler is to select a member function from this class. Parameter types are used. void Derived :: f (int); is the only matching function with the correct name and parameters from the Derived class.
  • The narrowing of type conversion from float to int occurs.
+2
source

Since the argument types of the two functions differ, the class in the Derived class does not actually override one of Base . Instead, Derived::f hides Base::f (I don't have a standard with me at the moment, so I can't quote the chapter).

This means that when calling d->f(3.14f) compiler does not even consider B::f . It allows the call of D::f . However, when you call b->f(3.14f) , the only version the compiler can choose is B::f , since D::f does not override it.

Your reading If the value can not fit into the destination type, the behavior is undefined is incorrect. He says the meaning is not of type. Thus, the value 3.0f fits into the int, but 3e11 does not. In the latter case, the behavior is undefined. The first part of your quote is A prvalue of floating-point type can be converted to prvalue of any integer type. explains why d->f(3.14f) allowed D::f(int) - float can actually be converted to an integer type.

+1
source

Source: https://habr.com/ru/post/1442594/


All Articles