什么决定了类对象何时在 PHP 中被销毁?

Posted

技术标签:

【中文标题】什么决定了类对象何时在 PHP 中被销毁?【英文标题】:What determines when a class object is destroyed in PHP? 【发布时间】:2012-01-21 08:18:36 【问题描述】:

假设我们有课程CFoo。在以下示例中,CFoo::__destruct() 何时被调用?

function MyphpFunc()

  $foo = new CFoo();

  . . .

  // When/where/how does $foo get destroyed/deleted?

在本例中,当脚本退出MyPHPFunc 的范围时,是否会调用析构函数,因为$foo 将不再可访问?

【问题讨论】:

脚本被终止时(在关机序列期间)。 首先转到源代码:php 手册.. 引用:“只要没有对特定对象的其他引用,或者在关闭序列期间以任何顺序调用析构函数方法”因此,当您不再使用它或脚本结束/它被杀死时。 php.net/manual/en/language.oop5.decon.php 【参考方案1】:

在 PHP 中,所有值都保存在所谓的zvals 中。那些zvals 包含实际数据、类型信息和 - 这对您的问题很重要 - 引用计数。看看下面的sn-p:

$a = new B; // $a         points to zval(new B) with refcount=1
$b = $a;    // $a, $b     point to  zval(new B) with refcount=2 (+1)
$c = $b;    // $a, $b, $c point to  zval(new B) with refcount=3 (+1)
unset($a);  //     $b, $c point to  zval(new B) with refcount=2 (-1)

只要refcount 到达0zval 就会被释放并调用对象析构函数。

以下是refcount 到达0 的一些示例:

unset一个变量:

$a = new B; // refcount=1
unset($a);  // refcount=0 => __destruct!

但是:

$a = new B; // refcount=1
$b = $a;    // refcount=2
unset($a);  // refcount=1 => no destruct as refcount > 0, even though unset() was called!

离开函数(或方法)范围

function a() 
    $a = new B; // refcount=1
               // refcount=0 => __destruct! (as $a does not exist anymore)

脚本执行结束

$a = new B; // refcount=1
die();      // refcount=0 => __destruct! (on script execution end all vars are freed)
// doesn't need to be die(), can be just normal execution end

这些显然不是导致refcount 减少的所有条件,而是您最常遇到的条件。

另外我应该提一下,因为 PHP 5.3 循环引用也会被检测到。因此,如果对象$a 引用对象$b$b 引用$a 并且没有进一步引用$a$b 两者的refcounts 将是1,但它们仍然会被释放(和__destructed)。在这种情况下,虽然破坏的顺序是未定义的行为。

【讨论】:

还请注意,当使用运行时用户创建的函数 (create_function) 或在 eval 语句中运行代码时,垃圾收集可能会变得有点滑稽。例如:如果您在 eval 语句中声明了一个类以及它的方法(使用析构函数),则创建该类的全局实例。你会注意到析构函数永远不会被调用!那是因为在调用析构方法时,在eval退出后,它已经被垃圾回收器拾取,因此该函数不再存在。 (这可能是一个未记录的错误) @NikiC:谢谢。这很有帮助。 好吧,我所做的是 create_function(eval('class someclass__desstructor()some code global $a; $a=new someclass();')) 类似的东西...... @NikiC,您能否详细说明答案中的 GC 部分? 那么,$a = new B; $a = new B; 会看到B 的第一个实例在$a 的第二个分配时破坏?还是在 GC 运行/脚本结束时销毁?【参考方案2】:

PHP 5 引入了与其他类似的析构函数概念 面向对象的语言,例如 C++。析构方法将是 一旦没有其他对特定的引用就调用 对象,或在关闭序列期间以任何顺序。 - PHP Manual

如果您想查看实际过程,请you can run this code here。

<?php

class A

    public function __construct()  var_dump('Creating: '. get_class($this)); 
    public function __destruct()  var_dump('Removing: '. get_class($this)); 


class B extends A 

$A = new A();

/*
 * When this block is called later on
 */
function create_b()

    $B = new B();
 // At this point the function scope ends, and since $B is not referenced anymore it's removed.


var_dump('B is next');
create_b(); // Run above block, create, then destroy be
var_dump('B is now gone');

// At this point the PHP file parser ends, $A is destroyed since it's not used anymore

【讨论】:

【参考方案3】:

信息在manual,虽然有些神秘:

PHP 5 引入了与其他面向对象语言(例如 C++)类似的析构函数概念。只要没有其他对特定对象的引用,或在关闭序列期间以任何顺序调用,就会立即调用析构函数。

含义:当对象被销毁(= 例如unset())或脚本关闭时,将调用析构函数。

其他有用信息:

与构造函数一样,父析构函数不会被引擎隐式调用。为了运行父析构函数,必须在析构函数体中显式调用 parent::__destruct()。

即使使用 exit() 停止脚本执行,也会调用析构函数。在析构函数中调用 exit() 将阻止剩余的关闭例程执行。

【讨论】:

那么,在我的示例中,当脚本退出MyPHPFunc 的范围时,是否会调用析构函数,因为$foo 将不再可访问? @Jim 这是个好问题,答案是——我不知道! (但是,您应该在代码前面写上function,以便清楚您在问什么。)我的 猜测 将是它会在函数结束时被销毁 - 必须试用。我很想删除这个答案,因为它没有回答你的问题【参考方案4】:

了解的最好方法是测试。

然而,简单的答案是 __destruct 在垃圾清理期间被调用。粗略对任何人都没有帮助,因为垃圾清理是一个持续的过程,当没有可以调用它们的范围时清理局部变量。

然而,这里有一些示例代码,其结果完全解释了在脚本内部退出作用域时会发生什么。

<?php
class testingdestructor 
    public function __construct($num) 
        $this->num = $num;
    
    public function __destruct() 
        echo "I am number $this->num\n";
    

class testing2
    public function __construct($num) 
        $this->classtest = new testingdestructor($num);
    
    public function __destruct() 
        echo "I am not a number\n";
    

$iam1 = new testingdestructor(1);
$iam4 = new testing2(4);
function testfunction() 
    $iam2 = new testingdestructor(2);

testfunction();
$iam3 = new testingdestructor(3);
unset($iam1);

这组奇怪的类函数和变量的输出是这样的

I am number 2
I am number 1
I am number 3
I am not a number
I am number 4

这向我们展示了函数的结尾调用了 __destruct,就像 unset 一样,并且至少在实践中,脚本清理的结尾是按照创建的相反顺序完成的。

【讨论】:

【参考方案5】:

如果创建一个类的实例并使用该对象。在完成所有任务后,如果你调用析构函数并再次在下一行使用相同的对象来执行你将不再能够使用的其他任务。这意味着你的析构函数被成功调用

【讨论】:

以上是关于什么决定了类对象何时在 PHP 中被销毁?的主要内容,如果未能解决你的问题,请参考以下文章

类和对象—— 基本概念

什么体现了类的多态性?

设计模式总结

开心档之C++ 类 & 对象

存储在向量中的对象,它们何时被销毁?

java 类2