【问题标题】:Php Destruct Called TwicePhp Destruct 调用了两次
【发布时间】:2011-07-09 06:00:52
【问题描述】:

下面的代码说明了 destruct() 被调用了两次。我想知道为什么?

class A {
    function hi(){ echo 'hi'; }
    function __destruct(){
        echo 'destroy';
    }
}
class B{
    public $this_ = '';
    function __construct(){
        $this->this_ = new A;
    }
    function __call($method, $params) {
          return call_user_func_array(array($this->this_, $method), $params);
    }
}

$b = new B;
$b->__destruct();

输出:

destroydestroy

编辑

zneak 和 TomcatExodus 都是正确的。如果我只是:

[..code..]
$b = new B;
$b->__destruct();
print 'end of script';

输出将显示:

destroyend of scriptdestroy

【问题讨论】:

  • 嗯,我没有注意到 B 没有扩展 A。我认为它可能正在调用您调用它的实例中包含的每个对象的析构函数。
  • @TomcatExodus:这不是错字。 OP 说在 B 上调用 destruct 是在 A 属性上调用它。
  • @BoltClock;是的,实现并删除了评论。刚刚测试,由于类B 创建了类A 的实例,并且B 也使用__call() 路由到自包含的A 对象,因此__destruct()B 的调用被路由到A 的自包含实例。在脚本终止时,所有对象都会离开内存,A 对象会再次触发 destruct。我相信我们的答案仍然正确,只是情况有所不同。
  • @TomcatExodus:该死的魔法方法! :)

标签: php oop destructor


【解决方案1】:

调用 destruct 不会破坏对象。您第一次使用__destruct() 调用它,然后当 PHP 脚本终止时,它会在清理时再次调用它。

如果您希望在脚本终止之前销毁该对象,请unset() 它。您应该只会看到一个 destruct 调用。


具体来说,您的类B 创建了一个类A 的自包含实例。由于B 还通过__call() 将方法调用路由到A 对象,这就是为什么B 上的__destruct() 调用正在A 上调用__destruct()B 没有定义析构函数并传递调用。

【讨论】:

  • 是的,我错过了一些,但添加到评论中,我也会添加回答。
  • @BoltClock;谢谢!一点也不,精度是必要的:)
【解决方案2】:

由于B 没有__destruct 方法,而是调用__call 方法(您可以通过在__call 方法中添加类似echo "calling $method" 的内容来验证这一点),然后将其转发给您的A 对象。

但是,调用__destruct 不会销毁对象:它只是调用应该与其销毁相关联的清理代码。因此,一旦您到达脚本的末尾,当 A 对象实际上被销毁时,它的 __destruct 方法就会再次被调用。

如果要删除 B 对象,请使用 unset($b)

【讨论】:

  • 我不知道哪一个是你们两个的正确答案,TomcatExodus 和 zneak,因为你们都是对的。在调用 __destruct() 后进行简单的打印测试表明,在页面执行后类的破坏再次调用 __destruct() 导致打印出“destructend of scriptdestruct”。
  • @Xiquid 然后给TomcatExodus正确的答案标记,我在他之后回答了几分钟,他比我更需要声誉。
  • 还有一个问题,你们俩都说 unset($b),但是当读到这个时,unset() 不会释放资源,最好先调用 __destruct() 然后调用 unset(),这个可能是因为我有点困惑:php.net/manual/en/function.unset.php#98692
  • @Xiquid 很确定这是错误的。您可以使用一个简单的 sn-p 来测试自己,然后创建 unset 一个对象。在析构函数中放入一个echo 语句,在unset 调用之后再放入一个echo 语句,你会看到__destruct unset 调用期间被调用(相反那家伙怎么说)。唯一不会发生这种情况的情况是该对象还有其他引用(例如,$a = new A; $b = $a; unset($a); 不会调用析构函数,因为$b 仍然引用该对象)。
【解决方案3】:

手动调用destructor 是最糟糕的想法之一,尤其是在处理其他人的代码时。一个对象的逻辑从construct 开始,经过methods 并在destruct 结束。在destruct 上,对象可能需要进行一些清理并且variables 可能会失效。 在内部调用destruct 时,由于unset($Object) 成功,对象不再可用。当您手动执行此操作时,该对象仍然触手可及,但如果它自己进行了一些清理,则没有内部变量支持。

现在想一想,如果您在对象上调用一个依赖于您在手动调用 destruct 后过早失效的数据的方法会怎么样。它打破了整个逻辑! 所以总是unset() 让 PHP 做它的事。

手动销毁 (placement delete :)) 在 C++ 中非常棒,如果您知道自己在做什么,尤其是结合placement new。但是您必须在整个实现过程中保护自己,并确保在调用方法时您确实拥有数据。如果您手动管理内存,则必须在破坏中保护自己,而不是删除指针两次并在过程中崩溃。

C++ 中的内存管理太酷了!我讨厌 GC (垃圾收集器) :)

--- 大吵大闹 ---

【讨论】:

  • 嗨@CodeAngry!谢谢你的吐槽。 :) 是的,我明白这一点。调用 __destruct() 毫无意义。当我使用类包装器时,我总是在内部取消设置我的对象。感谢 C++ 中的内存管理技巧。
【解决方案4】:

您正在手动调用析构函数;但这并不意味着您正在删除该对象。你只是在调用一个方法,而那个方法只是和其他方法一样。

$b->__destruct() 的调用调用$b->this_ 的析构函数,因为$b 没有显式析构函数方法。

当脚本完成时,Zend 引擎调用所有实例化对象的析构函数,然后执行例行清理,这涉及调用包含对象的析构函数,即在销毁 $b 之后,必须清除 $b->this_ 并且,为此,引擎会自动调用其构造函数。

请注意,第二次调用不是由于$b 的破坏,而是由于A 实例的破坏。

手动销毁对象没有障碍,它确实释放了它的资源(除非对象被共享,然后 GC 不会销毁它,除非没有更多对它的引用;在 PHP 中没有弱引用)。

GC 工作示例: http://codepad.org/7JDBoOKY

对象在代码完成之前被破坏。如果不是,那么输出的顺序就会被颠倒。

【讨论】:

    猜你喜欢
    • 2017-06-25
    • 2013-01-21
    • 2012-09-08
    • 2016-04-27
    • 2019-02-27
    • 1970-01-01
    • 2019-12-04
    • 2018-09-15
    • 2012-10-10
    相关资源
    最近更新 更多