关于对象:Ruby-按值传递

Ruby - passing by value

在Ruby中,有没有一种方法可以按值而不是按引用传递对象?例如,

1
2
3
4
5
6
7
8
9
10
11
12
class Person
  attr_accessor :name
end
def get_name(obj)
  obj.name ="Bob"
  puts obj.name
end
jack = Person.new
jack.name ="Jack"
puts jack.name
get_name(jack)
puts jack.name

输出应为

1
2
3
Jack
Bob
Jack

而不是

1
2
3
Jack
Bob
Bob

任何帮助将不胜感激。


不。 Ruby通过引用传递,而不是值传递。

如果需要模拟按值传递,则可以使用Ruby \\的Object#clone方法。在这种情况下,您将执行以下操作:

1
2
3
4
5
def get_name(obj)
  new_object = obj.clone
  new_object.name ="Bob"
  puts new_object.name
end

这将对对象进行浅表复制。换句话说,将复制对象的实例变量,但不会复制变量引用的对象。如果您需要进行深层复制,则可以阅读此Stack Overflow帖子。 Ruby没有执行深层副本的单方法方法,但是该文章描述了如何使用编组和解组来制作深层副本。

clonedup的工作原理非常相似,但是有一些区别。根据文档:

对象#克隆

Produces a shallow copy of obja€"the instance variables of obj are copied, but not the objects they reference. Copies the frozen and tainted state of obj. See also the discussion under Object#dup.

对象#dup

Produces a shallow copy of obja€"the instance variables of obj are copied, but not the objects they reference. dup copies the tainted state of obj. See also the discussion under Object#clone. In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance.

This method may have class-specific behavior. If so, that behavior will be documented under the #initialize_copy method of the class.

您可以查看dup并克隆文档。

编辑

尽管我的回答可能给出了OP所要寻找的内容,但就引用或值传递的语义而言,它并不是严格正确的。有关更多讨论,请参见此页面上的其他答案和评论。您也可以在此处和本文的评论中查看讨论,以获取更多信息。


Ruby是按值传递的。总是。这是一个演示该事实的简单程序:

1
2
3
4
5
6
7
8
9
10
def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts"Ruby is pass-by-#{baz}"
# Ruby is pass-by-value

您所看到的只是共享的可变状态:如果一个对象是可变的,则可以对其进行突变,并且如果该对象可以通过多个引用看到,那么该突变将通过多个引用可见。就那么简单。就像我的朋友一样称呼我" J ?? rg "还是像妈妈一样称呼"儿子"都没关系,如果我剪头发的话,你们两个都会看到。

如果您不希望对象发生突变,则需要使它们不变。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person
  attr_reader :name

  private

  attr_writer :name

  def initialize(name)
    self.name = name
  end
end

def get_name(obj)
  obj.name ="Bob"
  puts obj.name
end

jack = Person.new('Jack')

puts jack.name
# Jack

get_name(jack)
# NoMethodError: private method `name=' called for #<Person:0xdeadbeef081542 @name='Jack'>

puts jack.name
# Jack

现在,您的Person对象不能再被其他对象更改。但是,请注意,您的Person对象引用的对象显然仍然可以更改:

1
2
3
4
5
6
7
8
9
jack.name << ' the Ripper'

puts jack.name
# Jack the Ripper

jack.name.replace('Bob')

puts jack.name
# Bob

如果要防止这种情况,则需要确保Person对象引用的所有对象也是不可变的。例如这样的:

1
2
3
4
5
6
7
8
9
10
class Person
  def initialize(name)
    self.name = name.freeze
  end
end

jack = Person.new('Jack)

jack.name << '
the Ripper'
# RuntimeError: can'
t modify frozen String

现在,您的Person对象是真正不变的。 (至少在具有像Ruby这样强大的反射功能的语言中也可以像" truly ")。

不幸的是,我们现在已经对别人做了同样的事情,我们试图保护自己免受攻击:我们正在变异他们的物体!在Person#initialize中,我们对通过freeze传入的String进行突变。如果创建Person的方法想要对String做其他事情,那么它们会带来令人讨厌的惊喜:

1
2
3
4
5
6
name = 'Jack'

jack = Person.new(name)

name << ' the Ripper'
# RuntimeError: can't modify frozen String

我们可以通过在制作freeze之前先制作String的副本来解决此问题:

1
2
3
4
5
6
7
8
9
10
11
12
class Person
  def initialize(name)
    self.name = name.dup.freeze
  end
end

name = 'Jack'

jack = Person.new(name)

name << ' the Ripper'
# => 'Jack the Ripper'

在@Michael的出色回答之上,以正式答案提出原始问题:

Is there a way to pass objects by value and not by reference in Ruby?

不。绝对不。不可能。真的不行,就算了吧。

或者更准确地说,如果我们对词汇表不屑一顾,Ruby将按值传递对对象的引用,因此传递的对象是可变的。

在Ruby中,您必须:

  • 根据需要手动克隆/复制输入和输出(请参阅Michael的答案);或者
  • 假设如果您决定不这样做,那么谁也不会搞砸您的班级内部。
  • ruby主义者倾向于选择选项2,因为内部结构是通过这种方式构建的(attr_reader等返回对象状态),由于性能原因,并且因为创建返回或操作的所有内容的深层副本在实践中绝非易事。

    回答J ?? rg \\的评论:

    1
    2
    3
    4
    def foo(bar) bar << ' is now mutated' end;
    baz = 'Baz';
    foo(baz);
    puts baz # Baz is now mutated

    添加有关冻结的其他注释/示例,因为这也会产生意外的结果:

    1
    2
    3
    foo = {foo: 'bar'}.freeze  # {:foo=>"bar"}
    foo[:foo] += 'baz'         # RuntimeError: can't modify frozen Hash
    foo[:foo] << 'baz'         # {:foo=>"barbaz"}