笔者在习句柄传递和转换时,发现有一些需要注意的点,因此总结了一下,顺便在这下边叭啦记录一下:
一、一般情况下父类句柄与子类句柄之间的类型转换
子类句柄可以赋值给父类句柄(但是父类句柄不可以赋值给子类句柄),如下:
super= sub;
其中:super为父类句柄,sub为子类句柄。
赋值后含义为:
- 此时的父类句柄super已经指向了子类的对象。
- 虽然父类的句柄super指向了子类,但它仍然是父类的句柄,因此用该句柄调用方法/属性时,只能访问到父类中的方法/属性。
那么如果想要调用子类中的函数时,应该怎么办呢?即需要将父类句柄转换成子类句柄。
$cast(sub, super);
系统函数$cast()是一个带返回值的函数;使用$cast(sub, super)将父类句柄super转化为子类句柄sub,如果转化成功,$cast(sub, super)会返回1,如果转化失败,$cast(sub, super)会返回0,但是不会报错。所以通过if语句或者断言assert来判断是否转化成功,然后进行下一步操作。
assert($cast(sub, super)); //assert判别
else $fatal("Parent class conversion failed!");
if(! $cast(sub, super)); //if判别
$error("Parent class conversion failed!");
那么,什么情况下会转化失败,什么情况下能转化成功呢?这是需要注意的地方:
$cast(sub, super)中的父类句柄super,必须已经指向了子类对象,在这样的情况下,才会成功转化。
如果传入$cast(sub, super)中的父类句柄super,只是指向父类对象,或者是悬空句柄,即没有指向任何对象,则会转化失败。
我们通过一段代码,来总结上述知识点:
//定义父类superclass
class superclass;
rand bit[31:0] src;
function void display(input string prefix=“ ”);
$display(“%superclass:src=%0d”,prefix,src);
endfunction
endclass
//定义子类subclass
class subclass extends superclass;
bit[31:0] sub_src;
function void display(input string prefix=“ ”);
$display(“%subclass: sub_src =%0d”,prefix, sub_src);
super.display(prefix);
endfunction
endclass
//调用类方法
superclass super;
subclass sub;
sub =new(); //创建subclass子对象
super =sub;//父类句柄指向子类句柄
$cast(sub, super);//父类句柄super转换成子类句柄sub,且能转化成功
super.display();//此时调用的就是子类subclass中的display
二、虚方法
在上述句柄传递,父类句柄指向子类对象 或者 父类转化为子类时转化失败,这样的情况都需要格外小心对待。因为此时拿父类的句柄去访问子类对象的成员方法往往会出错,而得不到想要的结果(父类的句柄无法访问子类的对象),那这样的话句柄的传递多半就失去了意义。
那有什么方法可以尽量避免上述问题吗?当然有。
在实际的编码过程中,我们需要父类句柄在调用方法时,可以在运行时确定自身指向对象的类型,进而再调用指向对象的正确的方法。可以通过虚方法(virtual)实现父类和子类方法的动态绑定(在SV中成为动态方法查找)。通过虚方法的使用来实现类成员方法调用时的动态查找,用户无需担心使用的是父类句柄还是子类句柄,因为最终都会实现动态方法查找,执行正确的方法。
这要怎么理解呢?修改上述代码为:
class superclass;//定义父类superclass
rand bit[31:0] super_src;
virtual function void display();//通过 virtual关键字定义虚方法display()
$display(“Transfer superclass, superclass:src = %0d”, super_src);
endfunction
endclass
class subclass extends superclass;//定义子类subclass
bit[31:0] sub_src;
function void display();
$display(“Transfer class, class:src = %0d”, sub_src);
super.display();
endfunction
endclass
//调用类方法
superclass super;
subclass sub;
super = new();//创建superclass对象
super. display();//调用superclass:: display();
sub =new(); //创建subclass子对象
sub. display(); //调用 subclass:: display();
super =sub;//父类句柄指向子类对象
super.display();//此时调用的是子类subclass中的display()
如果将父类superclass中的方法display()定义为虚方法(加关键字virtual),当程序调用该方法时,SystemVerilog会根据句柄指向对象的类型,而非句柄的类型来动态决定调用什么方法(即动态调用)。在上述代码最后的两行代码,super虽然是父类的句柄,但是指向的是子类的对象,所以调用的是子类的方法subclass::display()。这样通过虚方法的使用来实现类成员变量的动态查找,就尽量避免了上述由于句柄传递所造成的问题。
如果没有对父类中的display()使用virtual修饰,SystemVerilog会根据句柄的类型super (父类 superclass),而不是句柄指向的对象的类型(子类subclass)来调用方法,这就会导致最后两行代码调用的是父类的方法superclass::display(),而不会调用子类的方法这不是想要的结果。
需要注意的是:
- 上述使用虚方法来实现方法的动态调用时,子类中相应的方法的方法名、方法的返回类型以及方法的参数,必须和父类中的方法保持一致,否则子类定义的方法就是其他方法;
- 虚方法通过virtual声明,只需要声明一次即可;例如上面代码中,只需要将父类中superclass:: display();声明为virtual,而其子类则无需再次声明,当然再次声明来表明该方法的特性也是可以的。
在为父类定义方法时,如果该方法日后可能会被覆盖或者继承,那么应该声明为虚方法,添加关键词virtual。
另外,面向对象的编程(OOP)中,上述这种子类中的方法和父类中的方法使用同一个的名字的现象叫做“多态(polymorphism)”。