问题的历史:
运算符重载(overloading)和使用魔术方法是Perl的一种高级特性,对于创建自己的对象抽象、支持算术、比较和字符串转换的复杂类型至关重要。Perl最初并不针对面向对象编程和运算符重载,但自5.0版本起,可以通过pragma overload扩展对象的标准行为,并引入特殊方法。
问题:
关键的微妙之处在于,机制非常灵活,但容易出错:重载对象的行为并不总是直观,错误的重载会导致无限递归、不必要的转型和上下文错误。大多数错误来源于字符串和数字的混合重载,以及意外调用未定义的运算符方法。
解决方案:
使用严格的pragma overload来定义所需的运算符,清楚地描述对象转换的方式,预测在两种上下文(数字和字符串)中的工作,并明确描述回退。建议处理所有预期的运算符,并谨慎对待重载的继承。
代码示例:
package MyNum; use overload '+' => 'add', '""' => 'as_string'; sub new { my ($class, $value) = @_; bless { val => $value }, $class; } sub add { my ($self, $other, $swap) = @_; my $sum = $self->{val} + (ref($other) ? $other->{val} : $other); return __PACKAGE__->new($sum); } sub as_string { my $self = shift; return $self->{val}; } 1; # 使用示例 my $a = MyNum->new(5); my $b = MyNum->new(7); my $c = $a + $b; print "$c "; # 12
关键特点:
陷阱问题1:当仅对字符串上下文的运算符(""")进行重载时,数字比较是否会自动工作?
不会,需要明确描述数字转换(0+),否则Perl会尝试通过字符串方法进行转换,这并不总是会产生预期结果(例如,对于eq/==有不同的行为)。
陷阱问题2:重载对象是否会支持在pragma overload中未明确列出的运算符?
不会,只有那些明确列出的运算符才会支持。其他的将使用基础行为或导致错误。
陷阱问题3:重载的运算符是否可以返回简单标量而不是对象?
可以,但会失去方法链和对象性,这会导致后续代码中的工作错误:要么重载不再适用于后续操作,要么逻辑会出现故障。
开发者仅为自己的对象重载了运算符"+"和"",却忘记了0+、-、cmp。在通过"=="进行比较时,得到了不正确的结果,因为触发了字符串化,而不是所需的转换。
优点:
缺点:
开发者使用pragma overload覆盖了所有需要的运算符(", 0+, +, -, x, <=>),并且对于不兼容的运算符抛出带有信息的异常。
优点:
缺点: