6

Ruby 用の C++ 拡張機能を作成しているときに苦労している 1 つの問題は、ユーザーがばかげたことをしても本当に安全にすることです。その場合、例外が発生するはずですが、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++ でチェックし、そうでない場合は例外をスローします。

Ruby C API を直接使用したことがありますが、それは簡単でした。ヌル ポインターと、allocate() 関数で false に設定されたフラグで構成されるダミー オブジェクトを割り当てました。また、initialize メソッドで、実際のオブジェクトを割り当てて、フラグを true に設定しました。すべてのメソッドで、そのフラグをチェックし、false の場合は例外を発生させました。ただし、Ruby C API を使用して多くのばかげた反復コードを作成しました。まず、C からアクセスできるように C++ クラスをラップし、次に Ruby 型をラップおよびアンラップする必要がありました。さらに、この愚かなフラグをチェックする必要がありましたすべての方法を試してみたので、Rice に移行しました。これは本当に素晴らしいことで、とてもうれしいです。

ただし、Rice では、プログラマーは、rice によって作成された initialize() 関数で呼び出されるコンストラクターしか提供できず、allocate() 関数は事前定義されており、何もしません。これを変更したり、「公式の」方法で独自の割り当て関数を提供したりする簡単な方法はないと思います。もちろん、C API を使用してアロケート関数を定義することもできたので、C API と Rice を何とか混ぜてみましたが、非常に厄介になり、奇妙な SegFaults が発生し、非常に醜いので、そのアイデアを放棄しました。 .

ここにライスの経験がある人はいますか、それともこれを安全にする方法を知っている人はいますか?

4

4 に答える 4

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、初期化から呼び出す場合、元の初期化メソッドが 2 回呼び出されることになります。これを防ぐ必要がある場合があります。

から例外をスローしmethod_addedて、悪いことをしていることをユーザーに警告することもできますが、これはメソッドの追加を停止しません。クラスは現在不安定な状態にあります。もちろん、元の初期化メソッドをそれらの上に再エイリアス化することもできます。

于 2012-10-03T09:07:30.187 に答える
2

あなたのコメントでは、C++ コードでthisは null ポインターであると言っています。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​​ では避けたほうがよいでしょう。

于 2012-10-03T08:37:00.047 に答える
0

これはRiceライブラリの問題だと思います。文書化されている方法でライスを使用すると、これらの問題が発生し、それを解決する明確な方法がなく、すべての回避策に欠点があり、ひどいものになります. したがって、バグレポートを無視しているように見えるので、解決策はライスをフォークしてこれを修正することだと思います。

于 2012-10-05T15:53:40.147 に答える