Странное поведение с конструкторами в расширении Ruby C - PullRequest
2 голосов
/ 08 декабря 2011

Я вижу странное поведение с конструкторами классов в расширении Ruby C.
Смотрите пример: у нас есть класс Foo, который является расширением C, и класс Bar, который наследуется от Foo:

extconf.rb

# extconf.rb
require 'mkmf'
create_makefile('foo/foo')

foo.c

// foo.c
#include "ruby.h"
#include <stdio.h>

VALUE
foo_new (VALUE class)
{
  printf ("foo_new\n");
  int *ptr;
  VALUE tdata = Data_Wrap_Struct (class, 0, 0, ptr);
  rb_obj_call_init (tdata, 0, 0);
  return tdata;
}

VALUE
foo_init (VALUE self)
{
  printf ("foo_init\n");
  return self;
}

VALUE
foo_plus_one (VALUE self, VALUE x)
{
  printf ("foo_plus_one\n");
  return INT2FIX (FIX2INT (x) + 1);
}

void
Init_foo ()
{
  VALUE foo = rb_define_class ("Foo", rb_cObject);
  rb_define_singleton_method (foo, "new", foo_new, 0);
  rb_define_method (foo, "initialize", foo_init, 0);
  rb_define_method (foo, "plus_one", foo_plus_one, 1);
}

bar.rb

# bar.rb
require './foo'

class Bar < Foo
end

Хорошо, давайте посмотрим странные вещи ...
В этой ситуации все идет хорошо:

x = Bar.new

мы получаем 2 отпечатка: foo_new и foo_init.
Хорошо, НО, если мы изменим класс Bar таким образом:

# bar.rb
require './foo'

class Bar < Foo
  def initialize(param = 1)
  end
end

у нас есть первые странные вещи, если мы запускаем

x = Bar.new

мы получаем только 1 отпечаток: foo_new. И foo_init ??
Хорошо, мы можем обойти эту проблему, добавив явный вызов к конструктору Foo:

# bar.rb
require './foo'

class Bar < Foo
  def initialize(param = 1)
    super()
  end
end

Мы получаем 2 отпечатка: foo_new и foo_init, если мы позвоним x = Bar.new.

Вторая странная вещь такова: если мы позвоним

x = Bar.new(2)

мы получаем ошибку

in `new': wrong number of arguments(1 for 0) (ArgumentError)

Но конструктор Bar принимает один параметр со значением по умолчанию.
Почему это? Это ошибка Ruby?

(протестировано с ruby1.9.3-p0 [x86_64])

1 Ответ

3 голосов
/ 08 декабря 2011

Вы определили ::new, чтобы не принимать аргументов, поэтому ожидается wrong number of arguments(1 for 0).В любом случае, ::new не должно быть переопределено.Правильный способ сделать это - определить метод ::allocate (::new вызывает его внутри).

Это должно работать:

// foo.c
#include "ruby.h"
#include <stdio.h>

void foo_free (void *ptr) {
    free (ptr);
}

VALUE foo_alloc (VALUE class) {
    printf ("foo_alloc\n");
    int *ptr = malloc(sizeof(int));
    return Data_Wrap_Struct (class, 0, foo_free, ptr);
}

VALUE foo_init (VALUE self) {
    printf ("foo_init\n");
    return self;
}

void Init_foo (void) {
    VALUE cFoo = rb_define_class ("Foo", rb_cObject);
    rb_define_alloc_func (cFoo, foo_alloc);
    rb_define_method (cFoo, "initialize", foo_init, 0);
}
...