我在为Ruby编写C ++扩展时遇到的一个问题是,即使用户做了愚蠢的事情,也要确保它真正安全。他应该获得例外,但绝不会是SegFault。具体问题如下:我的C ++类有一个非平凡的构造函数。然后我使用Rice API来包装我的C ++类。如果用户在他的Ruby代码中重新定义了initialize(),则会覆盖Rice创建的initialize()函数,并且既不分配也不初始化该对象。一个玩具示例如下:
class Person {
public:
Person(const string& name): m_name (name) {}
const string& name() const { return m_name; }
private:
string m_name;
}
然后我像这样创建Ruby类:
define_class<Person>("Person")
.define_constructor(Constructor<Person, const string&>(), Arg("name"))
.define_method("name", &Person::name);
然后,以下Ruby代码会导致Segfault
require 'MyExtension'
class Person
def initialize
end
end
p = Person.new
puts p.name
我会很高兴有两种可能:禁止以某种方式覆盖Ruby中的初始化函数或检入C ++,如果Object已正确分配,如果没有,则抛出异常。
我曾经直接使用Ruby C API然后很容易。我刚刚在allocate()函数和initialize方法中分配了一个由Null指针和一个设置为false的标志组成的虚拟对象,我分配了真实对象并将标志设置为true。在每个方法中,我检查了该标志并引发异常,如果它是假的。但是,我用Ruby C API写了很多愚蠢的重复代码,我首先必须包装我的C ++类,以便它们可以从C访问,然后包装和解包Ruby类型等,另外我必须检查这个愚蠢的标志每一种方法,所以我迁移到赖斯,这真的很好,我很高兴。
但是,在Rice中,程序员只能提供一个在rice创建的initialize()函数中调用的构造函数,而allocate()函数是预定义的,什么都不做。我不认为有一种简单的方法可以改变这个或以“官方”方式提供自己的分配功能。当然,我仍然可以使用C API来定义分配函数,所以我试图以某种方式混合C API和Rice,但后来我真的很讨厌,我得到了奇怪的SegFaults并且它真的很难看,所以我放弃了这个想法这里有没有人有赖斯的经验,或者有人知道如何安全吗?
答案 0 :(得分:4)
这个怎么样
class Person
def initialize
puts "old"
end
alias_method :original_initialize, :initialize
def self.method_added(n)
if n == :initialize && !@adding_initialize_method
method_name = "new_initialize_#{Time.now.to_i}"
alias_method method_name, :initialize
begin
@adding_initialize_method = true
define_method :initialize do |*args|
original_initialize(*args)
send method_name, *args
end
ensure
@adding_initialize_method = false
end
end
end
end
class Person
def initialize
puts "new"
end
end
然后调用Person.new
输出
old
new
即。我们的旧初始化方法仍然被称为
这使用method_added
挂钩,只要添加(或重新定义)方法,此时新方法已经存在,就会调用它,因此要阻止它们执行此操作为时已晚。相反,我们使用新定义的initialize
方法(您可能希望更加努力地确保方法名称是唯一的),并定义另一个初始化,首先调用旧的初始化方法,然后再调用新的初始化方法。
如果这个人是明智的并且从他们的初始化调用super
那么这将导致你的原始初始化方法被调用两次 - 你可能需要防范这个
你可以从method_added
抛出异常来警告用户他们做了一件坏事,但这并没有阻止添加该方法:该类现在处于不稳定状态。当然,你可以在它们之上实现原始的初始化方法。
答案 1 :(得分:2)
在你的评论中,你说在c ++代码中,this
是一个空指针。如果可以从ruby那里调用c ++类,我担心没有真正的解决方案。 C ++并非设计为万无一失。基本上这发生在c ++;
Person * p = 0;
p->name();
一个好的c ++编译器会阻止你这样做,但你总是可以用编译器无法检测到发生的事情的方式重写它。这会导致未定义的行为,程序可以执行任何操作,包括崩溃。
当然,您可以在每个非静态函数中检查这一点;
const string& Person::name() const
{
if (!this) throw "object not allocated";
return m_name;
}
为了简化并避免双重代码,请创建#define
;
#define CHECK if (!this) { throw "object not allocated"; }
const string& name() const { CHECK; return m_name; }
int age() const { CHECK; return m_age; }
然而,最好避免在ruby中用户可以重新定义初始化。
答案 2 :(得分:1)
这是一个有趣的问题,不是Rice的特有问题,而是任何将分配和初始化分离为单独方法的扩展。我没有看到明显的解决方案。
在Ruby 1.6天,我们没有分配/初始化;我们有新的/初始化。 Rice中可能仍然存在代码,它定义了MyClass.new而不是MyClass.allocate和MyClass #initialize。 Ruby 1.8将分配和初始化分离为单独的方法(http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/23358),Rice使用新的“分配框架”。但它有你指出的问题。
allocate方法不能构造对象,因为它没有传递给构造函数的参数。
Rice可以定义.new(就像在1.6上所做的那样),但这不适用于#dup和Marshal.load。但是,这可能是更安全(和正确)的解决方案。
答案 3 :(得分:0)
我现在认为这是Rice图书馆的一个问题。如果你以记录的方式使用Rice,你会遇到这些问题并且没有明显的方法可以解决它,并且所有的变通方法都有缺点并且非常糟糕。所以我想解决方案是分叉Rice并修复它,因为它们似乎忽略了错误报告。