2

我有一个对象,它是一个基本的树。我需要对其进行深层复制,并发现自己实现了 __clone 方法。成功的代码是:

function __clone() {
    $object = new CustomXML($this->rootElement);
    foreach ($this->elements as $key => $element) {
        $this->elements[$key] = clone $this->elements[$key];
        $object->elements[$key] = $this->elements[$key];
    }
    $object->attributes = $this->attributes;
    $object->value = $this->value;
    $object->allowHTML = $this->allowHTML;
    $object->endTag = $this->endTag;
    $object->styles = $this->styles;
    $object->childID = $this->childID;

    return $object;
}

我的问题是......为什么我必须使用

$this->elements[$key] = clone $this->elements[$key];
$object->elements[$key] = $this->elements[$key];

为什么我不能只使用

$object->elements[$key] = clone $this->elements[$key];

使用第二个仍然会留下对孩子的引用。为什么是这样?$this->elements 中的值属于同一类。

4

2 回答 2

2

__clone()已创建的对象浅表副本上调用。请参阅文档

PHP 将浅拷贝所有属性并创建一个新对象,而不调用其构造函数(类似于序列化和反序列化)。然后 PHP 调用对象,以便您可以__clone()根据自己的想法对其进行修改。因为它修改了对象,所以它不应该返回任何东西。

你的代码(如果你总是想要一个深拷贝)应该是这样的:

   function __clone() {
        foreach ($this->children as $key => $child) {
            $this->children[$key] = clone $this->children[$key];
        }
   }

但是我强烈建议您不要这样做!不要依赖clone关键字,而是添加方法以显式返回克隆对象(例如,DOMNode::cloneNode()。这将让您控制副本应该是浅的还是深的。如果您只是使用clone,则无法控制它。

这是一个例子:

interface DeepCopyable
{
    /**
     * Return a copy of the current object
     *
     * @param $deep bool If TRUE, return a deep copy
     * @return object
     */
    public function copy($deep=false);
}

class TreeNode implements DeepCopyable
{
    private $I_AM_A_CLONE = false;
    protected $children = array();

    function __clone() {
        $this->I_AM_A_CLONE = true;
    }

    public function addChild(Copyable $child) {
        $this->children[] = $child;
    }

    public function copy($deep=false) {
        $copy = clone $this;
        if ($deep) {
            foreach ($this->children as $n => $child) {
                $copy->children[$n] = $child->copy($deep);
            }
        }
        return $copy;
    }
}


$a = new TreeNode();
$a->addChild(new TreeNode());
var_dump($a);
var_dump($a->copy());
var_dump($a->copy(true));

此示例还说明了正确使用__clone(). 当克隆对象的私有或受保护属性不应与原始属性相同时,您需要添加克隆魔术方法。例如,如果您id在一个对象上有一个应该是唯一的属性,您可能希望确保克隆不会具有相同的 ID,并且您永远不想调用代码来控制它。或“脏”标志,或其他任何东西。

于 2012-12-14T19:13:54.760 回答
1

经过长时间的回顾,我创建了一个测试场景,并意识到我根本不了解克隆方法。

考虑这个示例代码:

<?php
    class A {
        function __construct($value = "1") {
            $this->value = $value;
            $this->children = array();
        }

        function addChild() {
            $this->children[] = new A(count($this->children));
        }

        function __clone() {

            foreach ($this->children as $key => $child) {
                $this->children[$key] = clone $this->children[$key];
                //$object->children[$key] = clone $this->children[$key];
            }
        }
    }

    $a = new A();
    $a->addChild();

    $b = clone $a;

    var_dump($b);
    $b->value = "test";
    $b->children[0]->value = "test2";

    var_dump($a);
    var_dump($b);

正在调用$bclone 方法,而不是在 $a 上。所以本质上,调用$this->children[$key] = clone $this->children[$key];就是破坏引用。在这里返回一个值是没有意义的。总之,我的代码应该是这样的:

foreach ($this->elements as $key => $element) {
    $this->elements[$key] = clone $this->elements[$key];
}

你可能会说调用$b = clone $a;相当于做:

$b = $a;
$b->__clone();
于 2012-12-14T18:37:44.920 回答