【问题标题】:Perl: How to deep copy a blessed object?Perl:如何深度复制受祝福的对象?
【发布时间】:2013-05-04 02:24:46
【问题描述】:

我正在寻找一个受祝福对象的深层(此时,浅层可能就足够了)副本。

Foo 类

package Foo;
our $FOO = new Foo;       # initial run

sub new {
   my $class = shift;
   my $self  = {};
   bless $self, $class;
   return $self;
}

主程序

use Foo;
my $copy = $Foo::FOO;     # instead of creating a ref, want to deep copy here
$copy->{bar} = 'bar';

bar 出现在 $Foo::FOO$copy 中。我意识到我可以通过将其设置为$copy = { %{$Foo::FOO} } 来创建该对象的副本,但随后它将不再受到祝福;此外,这仅适用于简单的数据结构(现在不是问题)。是不是只能这样复制,然后祝福(如$copy = bless { %{$Foo::FOO} }, q{Foo};)?

我尽量避免使用 Moose、Clone 或其他非核心模块/包,因此请在回复时记住这一点。更多:)

【问题讨论】:

  • 接受的解决方案可能会改变:注意它更适合浅拷贝简单的数据结构,但不能解决深拷贝,甚至更高级的类结构;这是我最初的问题。请记住,所选答案将来可能会发生变化。

标签: perl oop deep-copy bless


【解决方案1】:

对不起,我没注意到这句话:

*我试图避免使用 Moose、Clone 或其他非核心模块/包,因此请在回复时记住这一点。加粗使其更加突出:) *

所以这个答案不能被接受!

#!/usr/bin/env perl -w
use strict;
use warnings;
use Storable;
use Data::Dumper;

my $src = {
  foo => 0,
  bar => [0,1]
};

$src -> {baz} = $src;
my $dst = Storable::dclone($src);
print '$src is : '.Dumper($src)."\n";
print '$dst is : '.Dumper($dst)."\n";

【讨论】:

  • 嗨,Rahil Wazir,有网址:search.cpan.org/~ams/Storable-2.45/Storable.pm,您可以找到:“Storable 为您提供了一个 dclone 接口,它不会创建该中间标量,而是将结构冻结在一些内部";希望对您有所帮助。
  • 你能把这部分编辑成你的答案而不是评论吗?
【解决方案2】:

@chorba 的回答中的my $copy = bless { %$self }, ref $self; 是不够的。它只会克隆第一层。存储在$self 中的任何引用都不会被克隆。这样做的后果是……

$obj->{ponies} = [qw(Dash Sparkle Jack)];
$clone = $obj->clone;
push @{$clone->{ponies}}, "Pinkie";
print join ", ", @{$obj->{ponies}};  # Dash Sparkle Jack Pinkie

您现在可能没有任何参考资料,但以后可能会有。或者其他人会将一个插入您的对象。或者他们会继承并添加一个。

您可以编写深度克隆例程,但这并不简单。我强烈推荐使用Clone。它没有依赖项,因此您只需将 Clone.pm 复制到您的项目中即可。

另一个替代方案是@Zaid 提到的Storable::dclone,它已经在核心很长时间了。

无论您使用什么,在您的类上提供一个克隆方法都是正确的做法,即使它只是 Clone 或 Storable::dclone 的包装器。这将使您的对象的用户免受您的对象如何被克隆的详细信息。

【讨论】:

  • 我会说有时您会希望两个克隆对象保持指向同一个对象。例如,如果您的Person 类有一个employer 属性指向代表AcmeCorp 的对象Organization,那么您可能不希望克隆Bob 也克隆AcmeCorp。此类决定只能逐个类别做出,并应彻底记录在案。
  • @tobyink 是的,正是通过$obj->clone 而不是clone($obj) 进行克隆的原因。对象可以最好地决定它应该如何被克隆。 clone 方法应该做什么定义不明确。是浅克隆吗?深层克隆?一个“我认为合适”的克隆?应该考虑和定义克隆方法的行为/接口。您可能需要几种克隆方法。
【解决方案3】:

调用程序没有很好的方法知道“复制一个对象”意味着什么,所以一个对象应该知道如何复制自己。 Perl 的 OO 在这里没有为您提供任何帮助,但常规的做法是这样的:

package Car;

sub clone {
    my ($self) = @_;

    return $self->new(
        ( map { $_ => $self->$_() } qw/make model/ ), # built-in types
        engine => $self->engine->clone(), # copying an object
    );
}

【讨论】:

    【解决方案4】:

    use Storable 'dclone';?

    $ corelist Storable
    
    Storable was first released with perl v5.7.3
    

    此外,您还可以摆弄 Storable 挂钩,以更好地控制复制您的对象(不是我已经做到了,但这就是文档所声称的)。

    【讨论】:

      【解决方案5】:

      复制应该是 API 的一部分。您模块的用户永远不会知道在创建新对象时需要执行哪些特殊操作(考虑将每个对象注册到包中的 my 哈希中)。

      因此,为您的对象提供clone 方法。在里面,你可以使用任何你喜欢的肮脏技巧:

      sub clone {
          my $self = shift;
          my $copy = bless { %$self }, ref $self;
          $register{$copy} = localtime; # Or whatever else you need to do with a new object.
          # ...
          return $copy;
      }
      

      【讨论】:

      • 我认为这可能是要走的路。这将是我的下一步,我希望 Perl 有内置的东西可以做同样的事情,这样我就不必膨胀我的对象,但是谢谢!
      • 另外my $dictionary = clone $book; 读起来好多了。虽然这是公认的答案,但用户应该注意,这更多地适用于简单数据结构的浅拷贝,不能解决深拷贝,甚至更高级的类结构。
      • bless { %$self }, ref $self 技术只会进行浅拷贝。 $self 的引用属性不会被克隆。例如,$obj->{ponies} = [qw(Dash Sparkle Jack)]; $clone = $obj->clone; push @{$clone->{ponies}}, "Pinkie" 将修改这两个对象。
      • @Schwern:是的。我认为深度克隆是您必须使用的“肮脏技巧”之一。
      • @Schwern:这是浅拷贝的一个很好的例子。虽然,在几个小时前的评论中(这里和问题评论),我提到这是浅拷贝的公认答案。我可能最终会听取有关使用 CloneStorable 的建议,但我觉得如果我开始使用模块,我不妨将其 Moosify。
      猜你喜欢
      • 2014-01-19
      • 2011-01-20
      • 2017-09-25
      • 1970-01-01
      • 2011-11-14
      • 2013-07-07
      • 1970-01-01
      • 1970-01-01
      • 2011-07-26
      相关资源
      最近更新 更多