I’m trying to play with polymorphism and notice something interesting. Not sure if it is related to tool or it is the correct behavior defined in LRM. In following code, I define 3 classes:
A is the base, B extended from A, C extended from B. They all contain a function called showMsg;
In A, showMsg is non-virtual, it has no argument;
In B, showMsg is virtual, it has one argument, but no default;
In C, showMsg is virtual, it has one argument, default to 0;
I expect when I assign c to b, then do “b.showMsg()”, it should call showMsg from C. In other words, I don’t need to provide an argument as the default is 0. However, VCS gives me a compile error. I have to provide an argument to make it work, and in this case the implementation is indeed from C. So it seems polymorphism is for the implementation part, not the argument default?
Another thinking is that maybe VCS should give a compile error when I try to add a default to C::showMsg? Should the prototype (including argument default) be exactly the same for polymorphism?
module test;
class A;
function void showMsg();
$display("This is A");
endfunction
endclass
class B extends A;
virtual function void showMsg(int val);
$display("This is B, val = %0d", val);
endfunction
endclass
class C extends B;
function void showMsg(int val = 0);
$display("This is C, val=%0d", val+1);
endfunction
endclass
initial begin
A a;
B b;
C c;
b = new();
a = b;
a.showMsg();
c = new();
b = c;
b.showMsg(); // I get a compile error!
end
endmodule
Virtual method overrides in subclasses shall have matching argument types, identical argument names, identical qualifiers, and identical directions to the prototype. The virtual qualifier is optional in the derived class method declarations. The return type of a virtual function shall be either:
a matching type (see 6.22.1)
or a derived class type
of the return type of the virtual function in the superclass.It is not necessary to have matching default expressions, but the presence of a default shall match.
When you call b.showMsg, the compiler needs to ensure that the call is legal for any object that might be stored in the class handle b.
Dave, I have some long time confusions about up-casting and down-casting. Could you help clarify?
Like the code below, I have the same variables of three classes (line 1). At line 2, I create a class C object z, then up-cast twice from C to B, then to A. At line 3, I down-cast A back to C.
Question: at this step, I up-cast twice then down-cast back. Over the whole process, how object/handle changes? Handles x/y/z moves back and forth, which I can understand. How about the object itself? which is created only once. The instantiated object actually contains everything along the whole inheritance hierarchy? If a method is non-virtual, it can potentially has multiple copies of the method; if virtual there is only one copy (the last). During up-casting or down-casting, tool will pick the right implementation based on handle type (class) and virtual/non-virtual. Is my understanding correct?
In reply to eda2k4:
A cast to a class variable, or any kind of assignment to a class variable never changes the object; it only copies the class handle into the target class variable. $cast only adds a run-time check to make sure the handle is valid type to be stored in the class variable.
Class methods are never constructed or copied; they are part of the class type, just like static class members. Every class method you define exists regardless of whether they are virtual or non-virtual. The only difference is their visibility when they are referenced.
A class type provides information on how to look up everything in a class object by way of a set of mapping tables. There is a table for each virtual method. When you reference a virtual method with a class variable, the object type of the handle is used to look up which virtual method to call.
Could someone help explain this behavior difference? Shouldn’t functions that are virtual be always virtual? Does it matter where it is made virtual-parent/child class.
Variant 1.
module test;
class A;
virtual function void showMsg(int val);
$display("This is A");
endfunction
endclass
class B extends A;
function void showMsg(int val);
$display("This is B, val = %0d", val);
endfunction
endclass
class C extends B;
function void showMsg(int val);
$display("This is C, val=%0d", val+1);
endfunction
endclass
initial begin
A a;
B b;
C c;
b = new();
a = b;
a.showMsg(1);
c = new();
b = c;
b.showMsg(7);
end
endmodule
Result-
This is B, val = 1
This is C, val=8
Variant 2:
module test;
class A;
function void showMsg(int val);
$display("This is A");
endfunction
endclass
class B extends A;
virtual function void showMsg(int val);
$display("This is B, val = %0d", val);
endfunction
endclass
class C extends B;
function void showMsg(int val);
$display("This is C, val=%0d", val+1);
endfunction
endclass
initial begin
A a;
B b;
C c;
b = new();
a = b;
a.showMsg(1);
c = new();
b = c;
b.showMsg(7);
end
endmodule
Result -
This is A
This is C, val=8
The only difference between the two is the place where virtual keyword is used. Rest of the method prototype being exactly the same.
In variant 2, A::showMsg is non-virtual. The code calling a.showMsg must behave as non-virtual because is has no knowledge the class will be extended. That means A::showMsg could have a completely different prototype from B::showMsg if you wanted (i.e. a different number of argument). But once B::showMsg is declared virtual, all derived methods are virtual and must have the same prototype.
Does the virtual method lookup not work for variant 2? Isn’t that deduced at compile/elab stage? In this case, the prototypes of all the functions are exactly the same.