Project1

标题: 关于Viewport的诡异测试 [打印本页]

作者: 沉影不器    时间: 2009-9-15 20:07
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2009-9-15 23:10
clone 的调用是成功了的,克隆后的对象 id 也不同,只不过通过克隆体调用 set 或者 width= 的时候引用的是本体的变量,而调用本体的 width= 就冇问题了:
  1. v = Viewport.new(0,0,12,12)

  2. a = v.rect
  3. b = a.clone
  4. c = b.clone

  5. a.width = 9999

  6. p a, b, c
复制代码
可见至少 width 和 height 方法确实返回的是正确克隆后的宽度和高度变量
这个应该还是和 Rect 的属性在 C 中实现有关,因为 Rect 本身没有覆盖 Object 的 clone 方法,而 Sprite啊、Viewport啊之类的都覆盖了的,所以下面 p 出来是 false 的类都应该有这个问题:
  1. p "Rect #{Rect.instance_methods(false).include?("clone")}",
  2.   "Sprite #{Sprite.instance_methods(false).include?("clone")}",
  3.   "Bitmap #{Bitmap.instance_methods(false).include?("clone")}",
  4.   "Viewport #{Viewport.instance_methods(false).include?("clone")}",
  5.   "Plane #{Plane.instance_methods(false).include?("clone")}",
  6.   "Color #{Color.instance_methods(false).include?("clone")}",
  7.   "Font #{Font.instance_methods(false).include?("clone")}",
  8.   "Window #{Window.instance_methods(false).include?("clone")}",
  9.   "Tilemap #{Tilemap.instance_methods(false).include?("clone")}",
  10.   "Tone #{Tone.instance_methods(false).include?("clone")}",
  11.   "Table #{Table.instance_methods(false).include?("clone")}"
复制代码

作者: 沉影不器    时间: 2009-9-16 21:32
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2009-9-16 23:04
哦,原来覆盖是为了禁止 clone,还真不知道~

……不能 Marshal 确实诡异,无源码无从分析
不过我发现只要不是在 RGSS 类内部(比如 Viewport 的构造函数)分配的某个属性对象,至少 clone 可以正常工作了:
  1. a = Rect.new(0, 0, 12, 12)
  2. v = Viewport.new(0, 0, 1, 1)
  3. v.rect = a

  4. b = v.rect.clone
  5. b.width = 999

  6. p a, b
复制代码
另外不仅仅是 rect,只要是这些 RGSS 类的对象属性都有这个问题:
  1. s = Sprite.new

  2. a = s.color
  3. b = a.clone

  4. b.red = 88

  5. p a, b
复制代码
  1. p = Plane.new
  2. a = p.color

  3. b = a.clone
  4. b.red = 999

  5. p a, b
复制代码
对象 id 不同,说明在 Ruby 层的克隆成功了,只不过如果这个对象是在 C 层上分配的话,推测就是没有克隆数据,而是克隆了指针(?!)
作者: 霜冻之狼    时间: 2009-9-18 17:46
好东西,俺来膜拜下
作者: IamI    时间: 2009-9-18 17:53
如果我这么说阁下觉得可以理解吗
  1. def rect
  2. return Rect.new(x,y,width,height) # 伪代码,实际公式要比这个复杂= =
  3. end
  4. def rect=(value)
  5. # 同
  6. end
复制代码
这个方法只是为了方便使用而已……临时对象……结果自然可想而知
作者: link006007    时间: 2009-9-18 19:36
.clone方法返回的的确是Viewport.rect的属性的拷贝,
但是clone方法返回的是一个"冻结的,且会感染原实例"的实例(我英文不好-  -!!)...
也就是clone产生的实例被修改了,会影响到原实例. 但是源实例被修改了,则clone产生的实例不会受到影响

  1. a = Viewport.new(0,0,12,12)
  2. b = a.rect.clone
  3. a.rect.width = 24
  4. p a.rect, b, "ID:", a.rect.object_id, b.object_id, "Ins:", a.rect.inspect, b.inspect
  5. b.width = 48
  6. p a.rect, b, "ID:", a.rect.object_id, b.object_id, "Ins:", a.rect.inspect, b.inspect

  7. c = a.rect.clone
  8. a.rect.width = 12
  9. p a.rect, c, "ID:", c.object_id, "Ins:", c.inspect
  10. c.width = 48
  11. p a.rect, c, "ID:", c.object_id, "Ins:", c.inspect
  12. exit
复制代码
还有 Viewprot不能被clone,并不一定要重载clone方法
如果Viewport定义的ruby类具有const属性, 那么根据const的原则,是不可以存在一个可以感染它的clone实例,也就是Viewport不能被clone的原因

  1. VALUE
  2. rb_obj_clone(obj)
  3.     VALUE obj;
  4. {
  5.     VALUE clone;

  6.     if (rb_special_const_p(obj)) {
  7.         rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
  8.     }
  9.    ... ...
  10.    ... ...
  11.     return clone;
  12. }
复制代码
以上只是个人理解...  实际还要去看源代码- -
作者: 紫苏    时间: 2009-9-18 21:07
clone 只是会拷贝被克隆对象的污染和冻结状态,并不是克隆后必然产生污染和冻结的对象 ^^

  1. v = Viewport.new(0, 0, 12, 12)
  2. a = v.rect
  3. b = v.rect.clone

  4. p a.frozen?, b.frozen?
  5. p a.tainted?, b.tainted?
复制代码
另外即便是污染或者冻结,前者和安全等级有关,禁止了对对象的一些操作;而后者是使对象不可修改,“clone产生的实例被修改了,会影响到原实例. 但是源实例被修改了,则clone产生的实例不会受到影响”这个结论是从何而来?
作者: link006007    时间: 2009-9-18 22:43
“clone产生的实例被修改了,会影响到原实例. 但是源实例被修改了,则clone产生的实例不会受到影响”
紫苏 发表于 2009-9-18 21:07

我前面已经说了 这个只是我的理解...
Copies the frozen and tainted state of OBJ.
也就是第一段代码的运行结果.
a = Viewport.new
b = a.rect.clone
c = a.rect.clone
b或c修改,a的值会改变.   而a的值修改,b与c值不改变
再如

  1. class Klass
  2.    attr_accessor :str
  3. end
  4. s1 = Klass.new      #=> #<Klass:0x401b3a38>
  5. s1.str = "Hello"    #=> "Hello"
  6. s2 = s1.clone       #=> #<Klass:0x401b3998 @str="Hello">
  7. s2.str[1,4] = "i"   #=> "i"
  8. s1.inspect          #=> "#<Klass:0x401b3a38 @str=\"Hi\">"
  9. s2.inspect          #=> "#<Klass:0x401b3998 @str=\"Hi\">"
复制代码
但是如果单独的Rect.new.clone就不会有这种效果 = =
作者: 紫苏    时间: 2009-9-18 23:03
呵呵,我前面也说了,clone 只是拷贝被克隆体的污染和冻结状态而已,如果被克隆体本身就不是污染和冻结状态,克隆之后自然也不是……所以上面给出了调用 tainted? 和 frozen? 方法的代码,就是为了看克隆体是否有这两个状态的~

另外 Klass 这段代码和这贴讨论的不搭界——
首先 s1 是一个 Klass 的实例,并让其 str 成员指向了 "Hello" 对象;s2 是 s1 的克隆体,拷贝了其所有的成员变量,但这是一个浅层拷贝,s2 的 str 成员实际上指向的还是 s1 的 str 对象,也就是之前的那个 "Hello",换句话说就是这里的浅层拷贝仅仅拷贝的是对象在栈中的引用,而并没有在堆中实际拷贝一份对象的数据(值)~
  1. class Klass
  2.    attr_accessor :str
  3. end

  4. s1 = Klass.new
  5. s1.str = "Hello"
  6. s2 = s1.clone

  7. p s1.str.id, s2.str.id    # 对象 id 一致
复制代码
而这贴讨论的 Rect 问题,其属性按理说只有四个整型变量,那么普通的浅层拷贝就能做到值的拷贝了,但实际上还是有我们都看到的这个问题……(所以上面我才猜测可能是和拷贝了指向整型数据的指针有关)
作者: link006007    时间: 2009-9-18 23:10
本帖最后由 link006007 于 2009-9-18 23:19 编辑

哦  我在看看...

另外
str的id一样 是因为
像ruby这种高级语言 他们对字符串的处理都是用一个叫做"字符串常量池"的东西在维护.
即相同的字符串,他们其实引用的是一个字符串内存.. 这个和Java相似.
s1和s2应该是不一样的吧...
作者: 紫苏    时间: 2009-9-18 23:24
1、s1 和 s2 当然不一样,但是它们引用到的是一个对象,如果深层拷贝的话,他们引用的就应该是不同的对象
2、那你就错了,Java 的确有常量池,其字符串有保留(intern)机制,但 Ruby 没有~
  1. p "hello".id, "hello".id
复制代码
这里就生成了两个一模一样的 "hello",完全没有全局唯一性,只有取值范围在 31 位带符号整数之内的整型 Ruby 对象才有全局唯一性:
  1. p 1.id, 1.id
复制代码
3、是的,所以 Rect 的基本数据类型按理说应该可以直接浅层拷贝(而不是像拷贝字符串成员那样拷贝对象的地址),但这贴引出的问题则推翻了这个说法……
作者: link006007    时间: 2009-9-18 23:45
本帖最后由 link006007 于 2009-9-19 00:15 编辑

  ..是没有常量池... ...
我误解了不知道有多久呢... ...
我记得Ruby在定义String的结构体时,有一个shared字段... 还以为那是常量池...

不过ls知道... 为什么str.clone仅仅只是引用么?

  1. a = "hello"
  2. b = a.clone
  3. p a.object_id, b.object_id, "hello".object_id, "hello".clone.object_id
复制代码
这里的都不一样-  -
到是和Viewport的Rect挺像...
作者: 紫苏    时间: 2009-9-19 00:18
本帖最后由 紫苏 于 2009-9-19 00:39 编辑

str.clone 之后就不是克隆引用,而是克隆值了啊~
你之前克隆的是一个包含了 String 类型成员变量的类实例,浅层拷贝后拷贝的是 str 的引用,所以当修改这个 str 本身的时候,就会反映到所有这个对象的引用那里;str.clone 后当然就是真实的克隆体了(实际上深层拷贝就是递归去调用所有成员的 clone 方法)
  1. s1 = "hello"
  2. s2 = s1.clone

  3. s2 << " world"

  4. p s1, s2
复制代码
那四个对象,第一个是 a,第二个是 a 的克隆,第三个又是一个新的字符串对象了(尽管其内容和前面完全一样,但解释器一旦发现引号就会分配对象),第四个又是克隆,自然都不一样……

虽然 Ruby 的浮点数和长整数类型的实例也不具有唯一性,但直接浅层拷贝也可以,因为这些对象本身都是常量,是不可修改的,这就和 Java 的 String 一样了。即便内存中的常量存储区中有多份相同的拷贝,只要随便指向其中一个就行了,因为程序只需要它的值,而不关心他在内存中算老几,哦呵呵呵呵~

关于深层克隆,有兴趣可以参考:
http://rpg.blue/viewthread.php?tid=131787&highlight=clone
作者: link006007    时间: 2009-9-19 00:27
哦 我看错了
那个Klass是s1.clone 不是 s1.str.clone = =
我已开始就没搞对 T_T
作者: IamI    时间: 2009-9-19 08:42
总结结论就是……EB干了坏事吗囧
还是觉得就是临时对象在捣鬼= =
作者: 奶油Da蛋糕    时间: 2009-9-19 08:56
讨论的问题有点深奥=.=看不懂,飘过。=.=
作者: 沉影不器    时间: 2009-9-19 23:10
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2009-9-20 21:09
指针存储在栈上,拷贝的时候直接拷贝栈上的值,结果就是拷贝了指针指向的对象在堆中的地址,这就好像在 Java 中浅层克隆对象时,克隆了栈上的引用一样~

C 扩展的 RGSS 类,也就只有 Font 的几个类变量是 Ruby 变量了……
作者: 沉影不器    时间: 2009-9-20 22:02
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2009-9-20 22:06
本帖最后由 紫苏 于 2009-9-20 22:13 编辑

当然:
  1. p Rect.instance_variables
复制代码




10:11 编辑:
上面写错了,应该是——
  1. p Rect.new.instance_variables
复制代码

作者: 沉影不器    时间: 2009-9-27 21:11
提示: 作者被禁止或删除 内容自动屏蔽




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1