On 22.01.2017 04:53, Shiyao Ma wrote:
> Hi.
>
> A virtual function can return a pointer to the derived class.
>
> When it comes to multiple inheritance, the pointer might be casted to
> add some offset, in order to point to the right base.
Address adjustment can happen also for single inheritance.
> Base *ptr = someobjptr->some_func_return_pointer_to_derived();
>
> The problem is, virtual function binding is a runtime work. While,
> "Base*" type is a static work.
The declared return type is known at compile time.
> So how is the offset calculated to adjust the result of
> "someobj->some_func_return_pointer_to_derived()" to be "Base*" ?
There are a number of different cases.
> Though it's high impl. specific, any concrete example, like gcc?
The under-the-hood details are implementation specific, yes.
> The following is the code snippet, we can see the addresses of the
> two pointers is different.
>
>
http://ideone.com/6DmlT3
Usenet is not a web forum. Messages are archived and can be read for
many decades. Your URL will probably not be valid in a year or two.
As it happens Jens Thoms Toerring has already discussed your example
else-thread: your code creates two objects of two unrelated types, and
compares their addresses. It's an irrelevant example. :)
Here is about the simplest example where there is likely to be a pointer
adjustment when converting from `Derived*` to `Base*`:
struct Base { int x; };
struct Derived: Base { virtual ~Derived() {} };
#include <iostream>
using namespace std;
auto main()
-> int
{
Derived o;
Base& b = o;
cout << "Base at " << &b << ", Derived at " << &o << endl;
}
Since `b` is (apparently) a reference to `o`, an alias for `o`, one
might naively expect that they should be at the same address. But in
more detail `b` is a reference to /the `Base` sub-object`/ of `o`. I.e.
to something inside `o`, and since `Derived` is not a POD class that
sub-object is not guaranteed to be at the very start of `o`.
OTOH it's not guaranteed that there will be an adjustment, either: it
depends on the implementation. But it's likely. With MinGW g++ I get
Base at 0x22fd58, Derived at 0x22fd50
The Derived object logically contains a Base sub-object. And here the
g++ compiler placed a vtable pointer before the Base sub-object, at the
very start of Derived. Hence the Base sub-object is at a slightly higher
address than Derived, just sufficiently to make room for that vtable
pointer, which is 8 bytes with this 64-bit compiler.
So what happens if, in `Base`, you add this method:
virtual auto p() -> Base* = 0;
and in `Derived` you implement it as
auto p() -> Base* override { return this; }
Well that case is easy, in two ways! First, in `Derived` the type of
`this` is known to be `Derived*`, and the conversion to `Base*` can be
determined at compile time. And secondly, by adding a virtual method up
in `Base` we have introduced a vtable pointer there, so there's likely
no address adjustment at all, i.e., `Base` is at offset 0 in `Derived`.
To get an address adjustment sort of within the call of a virtual
method, one apparently needs multiple inheritance. At least for the in
practice.
Let's first construct an example with sub-objects of the same type but
at different offsets within the containing derived class object:
struct Base { int x; virtual ~Base(){} };
struct Intermediate1: Base {};
struct Intermediate2: Base {};
struct Derived: Intermediate1, Intermediate2 { };
#include <iostream>
using namespace std;
auto main()
-> int
{
Derived o;
Base& b1 = static_cast<Intermediate1&>( o );
Base& b2 = static_cast<Intermediate2&>( o );
cout << "Base 1 at " << &b1 << ", Base 2 at " << &b2 << ",
Derived at " << &o << endl;
}
With MinGW g++ I get
Base 1 at 0x22fd40, Base 2 at 0x22fd50, Derived at 0x22fd40
The problem with this example is that it's still irrelevant for the
virtual function call question, for it's not the case that a `Derived`
is-a single `Base`. A `Derived` here is two `Base`´s, a `Base` plus a
`Base`. And since they are on equal footing a `Derived*` doesn't convert
implicitly to single `Base*`: the conversion is ambiguous!
And so a virtual function implementation down in `Derived` cannot simply
return `this` in order to return a `Base*`: it must disambiguate, e.g.
via a `static_cast` as shown above, exactly which of the two `Base`
sub-objects it should return the address of.
The relevant conversion `Derived*` → `Base*` will therefore be known at
compile time, and it's also known at compile time for a virtual function
implementation in `Intermediate1` or `Intermediate2`.
To make things more complex & interesting we can sort of merge the two
`Base` sub-objects into a single shared one, by using `virtual`
inheritance from `Base`. All lines of `virtual` (direct) inheritance of
a class T go the same single T sub-object, and so in this code:
struct Base { int x; virtual ~Base(){} };
struct Derived_v: virtual Base {};
struct Derived1: Derived_v {};
struct Derived2: Derived_v {};
struct Most_derived: Derived1, Derived2 { };
#include <iostream>
using namespace std;
auto main()
-> int
{
Most_derived o;
Derived1& d1 = o;
Derived_v& d1v = d1;
Derived2& d2 = o;
Derived_v& d2v = d2;
Base& b = o;
cout << "b at " << &b << ", d1v at " << &d1v << ", d2v at " <<
&d2v << ", " << "o at " << &o << endl;
}
… the single `Base` sub-object must necessarily be at different offsets
in the two `Derived_v` sub-objects, and indeed, I get e.g. this output:
b at 0x22fd30, d1v at 0x22fd20, d2v at 0x22fd28, o at 0x22fd20
So now we're in position to ATTEMPT to create a virtual function with
covariant result, where that result must be adjusted in diffent ways
depending on through which sub-object that function is called.
That is, we attempt to force an adjustment of the function result that
depends on information only known at run-time:
struct Base
{
int x;
virtual auto p() -> Base* { return this; }
virtual ~Base(){}
};
struct Derived_v
: virtual Base
{
auto p() -> Derived_v* override { return this; }
};
struct Derived1
: Derived_v
{
auto p() -> Derived1* override { return this; }
};
struct Derived2
: Derived_v
{
auto p() -> Derived2* override { return this; }
};
struct Most_derived: Derived1, Derived2 { };
#include <iostream>
using namespace std;
auto main()
-> int
{
Most_derived o;
Derived1& d1 = o;
Derived_v& d1v = d1;
Derived2& d2 = o;
Derived_v& d2v = d2;
Base& b = o;
cout << "b at " << b.p() << ", d1v at " << d1v.p() << ", d2v at
" << d2v.p() << ", " << "o at " << &o << endl;
}
But this is just not allowed by the C++ rules: this code will not
compile with a standard-conforming compiler.
[C:\my\forums\clc++\050]
> g++ d.cpp
d.cpp:26:8: error: no unique final overrider for 'virtual Base*
Base::p()' in 'Most_derived
struct Most_derived: Derived1, Derived2 { };
^~~~~~~~~~~~
[C:\my\forums\clc++\050]
> cl d.cpp
d.cpp
d.cpp(26): error C2250: 'Most_derived': ambiguous inheritance of
'Derived1 *Base::p(void)'
[C:\my\forums\clc++\050]
> _
So, the short answer is that the C++ rules ensure that any adjustment of
a function result is completely known at compile time, when the function
implementation is compiled. And the slightly longer answer is that the
rules are quite complex, but they add up to reliable simple behavior. Or
at least, it's been that way up till and including C++14.
Cheers & hth.,
- Alf