Animal awhat the compiler seesPart 2 · Dispatch and abstraction
Trace calls from handle and object types, distinguish overriding from hiding, and use abstract classes and checked casts safely.
Question directory
Questions for Dispatch + abstraction
Open an authored answer, then use the adjacent object diagram or code example to verify the mechanism.
- inheritance and dispatchHow does a virtual method choose which implementation runs?SystemVerilog OOP→
- inheritance and dispatchWhat is the difference between overriding and hiding a method?SystemVerilog OOP→
- inheritance and dispatchWhat happens when an override calls super.method()?SystemVerilog OOP→
- inheritance and dispatchWhy do static methods and properties not dispatch polymorphically?SystemVerilog OOP→
- inheritance and dispatchWhy is calling a virtual method from a base constructor dangerous?SystemVerilog OOP→
- inheritance and dispatchHow do you solve a mixed virtual and non-virtual polymorphism trace?SystemVerilog OOP→
- abstract types and castingHow is a virtual class different from a virtual method?SystemVerilog OOP→
- abstract types and castingWhat does a pure virtual method require from derived classes?SystemVerilog OOP→
- abstract types and castingWhy is an upcast automatic while a downcast needs $cast()?SystemVerilog OOP→
- abstract types and castingWhat must match for a derived method to override a virtual method?SystemVerilog OOP→
- abstract types and castingCan a virtual class handle refer to a concrete derived object?SystemVerilog OOP→
SystemVerilog inheritance and dispatchSeparate handle type from object type.
Trace virtual calls, non-virtual hiding, super calls, static method resolution, and the constructor-time dispatch trap without guessing from the variable name.
The virtual keyword changes the lookup path
Dogwhat was allocatedDog::speak()woofRuntime dispatchThe base declaration marked the method virtual, so the object's dynamic type selects the implementation.
Virtual calls follow the dynamic object type. Non-virtual and static calls are bound from the declared type or class scope, even when the handle refers to a derived object.
When the base declaration is virtual, a call through any compatible handle dispatches from the dynamic type of the object. A base handle referring to a Dog therefore invokes Dog::speak().
Reason it through
- The handle's declared type controls which members are legal to name at compile time.
- The object's dynamic type controls which virtual override executes at runtime.
- Once a method is virtual in a base class, matching overrides remain virtual down the hierarchy.
class Animal;
virtual function string speak();
return "noise";
endfunction
endclass
class Dog extends Animal;
function string speak();
return "woof";
endfunction
endclass
Dog dog = new;
Animal animal = dog;
$display("%s", animal.speak()); // woofA derived method overrides a matching virtual base method and participates in runtime dispatch. A same-named method whose base declaration is non-virtual only hides that name; calls remain bound by the handle's declared type.
Reason it through
- Derived d = new; d.who() can call
Derived::whoeven when the method is non-virtual because d is declared Derived. - Base b = d; b.who() calls
Base::whowhen the base declaration is non-virtual. - Make the base method virtual and preserve the signature when polymorphic substitution is required.
| Base declaration | Call through Base handle | Call through Derived handle |
|---|---|---|
| virtual | Derived override | Derived override |
| non-virtual | Base method | Derived hidden method |
super.method() invokes the immediate superclass implementation directly from the derived method. It is useful for extending base behavior rather than replacing it completely.
Reason it through
- In
C::vf, super.vf() namesB::vfwhen C extends B. - If
B::vfalso calls super.vf(), that nested call reachesA::vf. - The call is selected lexically from the class hierarchy, not by searching the runtime object again.
class A;
virtual function string name(); return "A"; endfunction
endclass
class B extends A;
function string name();
return {"B(", super.name(), ")"};
endfunction
endclass
class C extends B;
function string name();
return {"C(", super.name(), ")"};
endfunction
endclass
// C object prints C(B(A))Static members belong to a class type, not an object. A derived same-named static member shadows a separate base member, and a call is resolved from class scope or the handle's declared type rather than the dynamic object type.
Reason it through
Base::inc()updatesBase::cntandDer::inc()updatesDer::cnt.- A Base handle referring to a Der object still resolves a static call as Base.
- In the deck's sequence,
Base::get()is 3,Der::get()is 120, and hb.get()resolves to the base value 3.
class Base;
static int cnt = 0;
static function void inc(); cnt++; endfunction
static function int get(); return cnt; endfunction
endclass
class Der extends Base;
static int cnt = 100;
static function void inc(); cnt += 10; endfunction
static function int get(); return cnt; endfunction
endclass
Base b = new;
Der d = new;
Base hb = d;
Base::inc(); Der::inc();
b.inc(); d.inc(); hb.inc();
// Base::get() = 3, Der::get() = 120, hb.get() = 3Virtual dispatch can enter the derived override while the base constructor is still running, before the derived constructor has initialized its fields. The override observes default or partially initialized derived state.
Reason it through
- If BaseB.new calls
build()and the object is DerB,DerB::buildcan run before DerB.new assigns ready = 1. - The printed ready value is therefore 0, the default for int, rather than the intended initialized value.
- Keep constructors non-polymorphic and call a separate build or configure phase only after construction completes.
class DerB extends BaseB;
int ready;
function new();
super.new();
ready = 1;
endfunction
function void build();
$display("ready=%0d", ready);
endfunction
endclass
DerB d = new;
d.build(); // safe: construction is completeFor every call, write down the handle's declared type, the object's dynamic type, and whether the base declaration is virtual. Virtual calls follow the dynamic type; non-virtual calls follow the handle type; super calls follow the immediate base implementation.
Reason it through
- An A handle referring to C calls
C::vfwhen vf is virtual butA::nfwhen nf is non-virtual. - After a successful
$castto B, the B handle still refers to the same C object; a non-virtual nf call now binds toB::nf. - A C handle binds the same non-virtual call to
C::nf, while every compatible handle still dispatches virtual vf to C.
| Call kind | Lookup source | Object copied? |
|---|---|---|
| virtual instance method | Dynamic object type | No |
| non-virtual instance method | Declared handle type | No |
| static method | Class scope or handle type | No |
| super.method() | Immediate superclass implementation | No |
SystemVerilog abstract types and castingProgram to a contract you can check.
Use virtual classes and pure virtual methods to define incomplete base types, then move through the hierarchy with safe upcasts and checked downcasts.
Abstract bases define the contract boundary
virtual class Shapepure virtual function area()Rectarea() implementedCirclearea() implementedRect handle→Shape handleupcast · always safeShape handle→Rect handle$cast() · runtime checkA concrete child must satisfy every inherited pure virtual contract. Upcasting preserves the object behind a base handle; $cast() verifies a downcast at runtime.
A virtual class is an abstract class type that cannot be instantiated directly. A virtual method is an instance method that supports runtime overriding; a concrete class may contain virtual methods and still be instantiable.
Reason it through
- A
virtual classmay contain implemented properties and ordinary or virtual methods. - Declaring a class virtual says the type itself is incomplete, even when it has no pure method.
- Declaring a method virtual changes dispatch for that method but does not make the class abstract.
| Construct | Main effect | Can enclosing class be instantiated? |
|---|---|---|
virtual class | Type is abstract | No |
| virtual method | Call dispatches dynamically | Yes, if class is concrete |
| pure virtual method | Derived concrete type must implement it | Only legal in an abstract class |
A pure virtual method declares a signature without an implementation. Every non-virtual derived class must provide a matching implementation or the derived class must remain virtual.
Reason it through
- The containing class must be declared virtual.
- A virtual intermediate class may defer the implementation to a later concrete child.
- Pure methods are useful for interface-style contracts such as
format(), send_pkt(), or area().
virtual class Shape;
pure virtual function int area();
endclass
class Rect extends Shape;
int w, h;
function new(int w = 1, int h = 1);
this.w = w;
this.h = h;
endfunction
function int area();
return w * h;
endfunction
endclassEvery derived object satisfies its base type, so assigning a derived handle to a base handle is safe. A base handle may refer to many possible dynamic types, so $cast() must verify a downcast at runtime.
Reason it through
- An upcast changes only the handle view; the same object remains allocated.
$cast(destination, source)returns 1 and assigns the handle when the dynamic object is compatible.- A failed cast returns 0 and should take an explicit error or alternate path.
Rect original = new(3, 4);
Shape base = original; // safe upcast
Rect recovered;
if ($cast(recovered, base))
$display("area=%0d", recovered.area());
else
$display("incompatible dynamic type");The derived method must preserve the base method's name and compatible prototype, including return type and formal argument contract. It then remains virtual even if the keyword is omitted in the derived declaration.
Reason it through
- Changing the argument list does not create a useful overload; SystemVerilog method overloading is not supported.
- Changing only the implementation is the normal override.
- Use a differently named helper when the derived class needs extra inputs beyond the base contract.
Yes. A virtual class cannot be constructed directly, but its handle may refer to any compatible concrete derived object and invoke the abstract contract polymorphically.
Reason it through
- Shape s; is legal because it declares only a handle.
- Rect r = new(3, 4); s = r; is a safe upcast.
- Calling s.area() dispatches to
Rect::areaand returns 12.
