2009-09-13 7 views
8

Ruby définit #clone dans Objet. A ma surprise, certaines classes déclenchent des exceptions lors de l'appel. Je trouve NilClass, TrueClass, FalseClass, Fixnum ayant ce comportement.Quelles classes Ruby prennent en charge .clone?

1) Existe-t-il une liste complète de classes (au moins de classes de base) qui n'autorise pas #clone? Ou existe-t-il un moyen de détecter si une classe spécifique prend en charge #clone?

2) Quel est le problème avec 42.clone?

+2

Je veux vraiment savoir comment tester si une classe est cloneable moi-même. Il semble que si une classe ne veut pas se permettre d'être clonée, elle devrait rendre privée la méthode clone héritée de l'objet afin que vous puissiez tester son existence uniquement sous public_methods. On dirait du bon sens pour moi. –

Répondre

7

Je ne pense pas qu'il y ait une liste formelle, du moins à moins que vous ne comptiez lire la source. La raison pour laquelle 2) ne fonctionne pas est due à une optimisation appliquée à Fixnums. Ils sont stockés/passés en interne comme leurs valeurs réelles (donc vrai, faux et nul), et non comme des pointeurs. La solution naïve est d'avoir 42.clone retourner le même 42, mais alors l'invariant obj.clone.object_id != obj.object_id ne tiendra plus, 42.clone ne serait pas réellement clonage.

+0

'obj.clone.object_id! = Obj.object_id' étant vrai et' obj. clone.object_id == obj.object_id 'n'étant pas toujours vrai sont différents. Que le premier ne tient pas ne veut pas dire que ce dernier ne l'est pas. – sawa

0

Vous ne pouvez pas cloner des classes immuables. C'est à dire. vous ne pouvez avoir qu'une seule instance de l'objet 42 (en tant que Fixnum), mais vous pouvez avoir plusieurs instances de "42" (car la chaîne est mutable). Vous ne pouvez pas non plus dupliquer des symboles, car ils sont quelque chose comme des chaînes immuables.

Vous pouvez vérifier cela dans IRB avec la méthode object_id. (les symboles et les fixnums vous donneront le même object_id après les appels répétitifs)

+1

La mutabilité n'a rien à voir avec cela (en fait, vous pouvez ajouter un état à un Fixnum). – Chuck

+0

Comportement par défaut vraiment bizarre pour Fixnum, d'autant plus qu'il possède la méthode clone, d.class.method_defined? (: Clone) == true –

5

Fixnum est une classe spéciale donnée un traitement spécial par la langue. Dès le lancement de votre programme, il y a exactement un Fixnum pour chaque numéro que la classe peut représenter, et ils reçoivent une représentation spéciale qui ne prend pas d'espace supplémentaire - de cette façon, les opérations mathématiques de base n'allouent pas et ne libèrent pas mémoire comme un fou. Pour cette raison, il ne peut pas y en avoir plus d'un 42.

Pour les autres, ils ont tous une chose en commun: ce sont des singletons. Par définition, il n'y a qu'une seule instance d'une classe singleton, donc essayer de la cloner est une erreur.

+1

"Pour cette raison, il ne peut y en avoir plus d'un 42". Et pourquoi aurait-il besoin d'être? C'est parfait. –

1

Je ne sais toujours pas comment tester clonabilité correctement, mais voici un très maladroit, mauvais chemin pour tester clonablity en utilisant la récupération d'erreur:

def clonable?(value) 
    begin 
    clone = value.clone 
    true 
    rescue 
    false 
    end 
end 

Et voici comment vous pouvez cloner même le inclonable. Au moins pour les quelques classes avec lesquelles je l'ai fatigué.

def super_mega_clone(value) 
    eval(value.inspect) 
end 

Voici quelques tests d'échantillons:

b = :b 
puts "clonable? #{clonable? b}" 

b = proc { b == "b" } 
puts "clonable? #{clonable? b}" 

b = [:a, :b, :c] 
c = super_mega_clone(b) 

puts "c: #{c.object_id}" 
puts "b: #{b.object_id}" 
puts "b == c => #{b == c}" 
b.each_with_index do |value, index| 
    puts "[#{index}] b: #{b[index].object_id} c: #{c[index].object_id}" 
end 
b[0] = :z 

puts "b == c => #{b == c}" 
b.each_with_index do |value, index| 
    puts "[#{index}] b: #{b[index].object_id} c: #{c[index].object_id}" 
end 

b = :a 
c = super_mega_clone(b) 
puts "b: #{b.object_id} c: #{c.object_id}" 

> clonable? false 
> clonable? true 
> c: 2153757040 
> b: 2153757480 
> b == c => true 
> [0] b: 255528 c: 255528 
> [1] b: 255688 c: 255688 
> [2] b: 374568 c: 374568 
> b == c => false 
> [0] b: 1023528 c: 255528 
> [1] b: 255688 c: 255688 
> [2] b: 374568 c: 374568 
> b: 255528 c: 255528 
1

J'ai fait une git grep "can't clone" du code source de YARV, et a obtenu

lib/singleton.rb: raise TypeError, "can't clone instance of singleton #{self.class}" 
object.c:  rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj)); 
test/test_singleton.rb: expected = "can't clone instance of singleton TestSingleton::SingletonTest" 

Les première et troisième lignes indiquent que vous ne pouvez pas cloner un singleton . La seconde ligne fait référence à rb_special_const_p(obj). Mais cela va au-delà de mon ken.

Questions connexes