Peter Duniho said:
[...]
Well, as I said in my other reply, "new" is what breaks the chain of
virtual inheritance.
Agreed.
The chain and the breaking of the chain is what is confusing.
I hate to break it to you, but in reality, there's not even a chain as
you're visualizing it. Inasmuch as your understanding of the way virtual
methods work is correct (and I think there are still flaws

), it's only
correct in a conceptual sense. The underlying implementation doesn't
involve any "chaining" at all.
And that was really what I am doing. I understand that the underlying
implementation is different.
But I assume that when a project if built, it would have to follow the chain
to be able to set up the pointers and vector tables you mention.
Not that it's not useful for you to learn how all this works, or even to
use the concept of chaining, but you should be careful to make sure you
understand this is strictly a high-level conceptual thing, describing the
OOP relationship of your classes, rather than having anything to do with
how the code is actually executing.
I do understand that it is a high-level concept, which is what I am trying
to do to understand it better. Then I will get into the pointers (which are
not really necessary to understand to deal with the classes).
One of the responses was correct but was confusing because I didn't have a
real chain set up. I had the 1st Class that followed the DrawObject as
"new". So the person correctly said that when that was set to new then
doa.Draw() would go to the DrawObject.Draw method. The problem is that
didn't help me understand how the chain worked. Once the "new" was added
down the chain, then it made more sense. It went to the DrawObject.Draw and
will move up the chain until you find either another "new or the actual
class (in my test case - Square.Draw()). I thought that if you had a class
in the chain that didn't have the method at all, it would keep going. I
thought not having that method would stop the chain as well as "new" but
when I took the Draw() method out of Triangle completely (so that there was
no "new" or override in this class or in the chain at all) it went all the
way to Square.Draw(). So I was wrong there. Only the "new" would stop the
chain unless ALL the classes up to Square had no Draw() method.
That said...on with the discussion:
Warning: that's a self-contradictory statement. I know what you mean, but
the _object_ is _always_ just whatever type it is. You can reference that
object with variables of any type that object inherits or implements, but
the object itself always _is_ whatever type it "really is".
So is:
DrawObject doa = new Square()
a DrawObject or a Square (DrawObject being the "type" - what would you call
the Square here)?
I think you should stick to letters for your type names. Using actual
geometry terms just confuses the issue (well, at least for me anyway),
because it's very difficult to turn off the part of the brain that
interprets those terms.
I only did that because that was the original test program I was using. And
at that point, each class didn't inherit from each other. They all
inherited from DrawObject. But I was trying different things to see what
would happen with the test I had.
When I did the last one with the letters, I thought about changning the
program to use the letters but thought THAT might be confusing since we have
been referring to geometric figures throughout this thread.
Using your original chain, we can rephrase the statement as:
if a variable "a" references an object of type E, "a.Draw()" would
actually call E.Draw() and not A.Draw();
If you say "a" references an object of type E, are you saying:
A a = new E();
or
E a = new A();
Just trying to get the order right here.
If you stick a "new virtual" Draw() method anywhere along this chain,
"a.Draw()" would call whatever Draw() method is the most-derived override
in the chain of inheritance up to but not including the class where you
stuck the "new virtual" Draw(). This may or may not be in the immediate
base class of the class where you stuck a "new virtual" Draw() method.
By most-derived override, you also mean up to the object whichever comes
first.
For example, if:
A a = new E()
a.Draw()
It would go from A towards e until it hits a wall ("new").
I think part of my problem was the example didn't have enough object for
the
chain to show what my confusion was.
[...]
If I leave like this,
My doa.Draw(), I assume goes to the DrawObject.Draw is of TYPE
DrawObject.
I don't understand that statement. It's not a grammatically parseable
sentence.
Maybe not but what I meant to say (what I was asking above), I assume that
doa is of TYPE DrawObject and not of TYPE Square when I say:
DrawObject doa = new Square();
I realize that now.
But if I take out the "finds a class NO Draw Method" out of the statement,
than I assume this correct - in a high-level concept.
Not exactly. Finding a class "with NO Draw method" doesn't affect which
method is called at all. For example:
class A
{
virtual void Method1() { }
}
class B : A
{
}
class C : B
{
override void Method1() { }
}
then:
A a = new C();
a.Method1();
This will call C.Method1(), not A.Method1().
I didn't realize that override would extend over a directly inherited class
(B in this case) to override A's Method1. I do now, though.
but if I stick a "virtual" on the method of any class (lets say the
triangle),
It's not the "virtual". It's the "new".
[...]
In this case, it would go to the "Type or Class" (DrawObject) and move up
the chain toward the actual object (Square) until it hit the Triangle
class
and would execute the Elipse.Draw().
For what it's worth, you're off by one on your types. Your example shows
a "new virtual" method in the Circle class, not the Triangle class.
You're right. Missed that.
But, as far as your description goes, yes...once you've got that "new
virtual" in a class, then you've got a whole new virtual method there, and
because it has the same name as the base virtual method, it hides it from
that type on in the inheritance chain.
I assume that by base virtual method, you don't necessarily mean the
immediately previous class but the first class going backwards down the
chain until the 1st method is found whether it is a "new" method or
"override" method. This was something I wasn't clear about, as well until I
tried it. I had assumed the "base" meant the Class that this class was
inheriting from directly.
So using your example:
class A
{
virtual void Method1() { }
}
class B : A
{
}
class C : B
{
base.Method1();
override void Method1() { }
}
Here base.Method1() would refer to A.Method1(), even if there was a class
before "A" that had a Method1().
So, if you have an object that's of that type or later in the inheritance
chain, then which method you get depends on the type of the variable used
to call the method, because different types see different methods. Types
from the very base class all the way up to the class just before the one
where you hid the base method will see the base method. Types from the
method where you hid the base method on will see the new method.
I have no idea if this will help you understand or not (it might just
confuse you more, and if so I apologize...you can ignore this last part of
my post if you like

), but I'll give you a little bit of the
implementation details of virtual methods. They are commonly implemented
in various languages using what's known as a "v-table". A v-table is
really just an array of function pointers.
When you declare a virtual method, that reserves a new slot (array
element) in the v-table for that method and stores a reference to that
class's implementation of the method in the slot. When you call a virtual
method, rather than calling the method directly, the compiler generates
code that will use the method reference found in the slot that goes with
the method name and signature. When a sub-class overrides a virtual
method, rather than creating a new slot, it just replaces the base class
reference with its own reference of its implementation, so that its method
is the one that gets called instead.
Which is what I said above (I think). During the build it would follow all
the chains and sets up the pointers at that points. I realize that it would
be more complicated than that, especially for objects set up dynamically:
objects set up in arrays at runtime, for instance.
Very important: for a given type, there is a one-to-one correspondence
between the method name and signature, and the v-table entry for that
method name and signature.
No matter how far down the inheritance chain you go, as long as no one
hides the virtual method, a new slot for the method isn't ever added to
the v-table. Each class just provides its own reference for the one slot
that was reserved and each class always uses that one slot when calling
the virtual method.
Now, when you hide a virtual method, that essentially creates a whole new
method, which means you do get another slot in the v-table. The thing is,
any time you execute code using the type that hid the method or a type
that inherits that type, you've _hidden_ the previous method. The slot in
the v-table that corresponds to the method that was hidden is,
well...hidden. No code, using those types, will ever be able to execute
that method, because the name and signature that goes with that method
refers instead to the new slot.
On the other hand, any time you execute code using a type earlier in the
inheritance chain than the type that hid the method, that code doesn't
know anything about the new method. It only knows about the original
virtual method, and so that code will always use the original v-table
entry for the method.
In other words, hiding a virtual method results in two different v-table
entries for the given method name and signature, and which entry is used
depends on the type of the _variable_ used to call the method (because
that's all the compiler knows at the point where it's generating code for
the call). If you use the base types, you get the original v-table
entry. If you use the type that hid the original method, or a type that
inherits that type, you get the new v-table entry.
"...and never the 'twain shall meet".
Finally, note that in the above I talk about just one hiding of a method,
but of course any class can hide any inherited method, and for virtual
methods, every time the method is hidden, that forces a new v-table entry
to be created. For a given method name and signature, any given type can
only ever see one implementation of that method name and signature at a
time, and so which implementation is called depends on the type of the
variable used to call it.
Good information.
Would be nice if there were better papers on this that show the v-tables and
how they work with inheritance with good diagrams and compared to the
high-level concepts to get a better idea on how it works. Typically, it is
a couple of classes with a "new" in the middle class and then a comment that
the chain is now broken. But without a chain with 6 or 7 "links", you
really don't see what is happening.
I think I have a much better handle on this, now.
I appreciate the explanations.
Thanks,
Tom