Referenced型オブジェクト¶
概要¶
Choreonoidでは動的メモリ確保によって生成されるオブジェクトの多くが、Referencedクラスを基底とするクラスとして定義されています。 そしてそのようなクラスはChoreonoid独自のスマートポインタであるref_ptr型のポインタで保持されます。 この形態はChoreonoid SDKの至るところで利用されていて、独自のプラグインを作る際に利用する機会も多いかと思います。 そこで本節ではそのようなReferenced型オブジェクトの生成と保持について、基本的な事柄を解説します。
オブジェクトの動的生成とスマートポインタ¶
生存期間が比較的長く、プログラムの複数の個所から共有されるようなオブジェクトは、通常new演算子によって生成し、動的に確保されたメモリ上に配置され、ポインタでアクセスすることになります。この場合、オブジェクトが必要でなくなったタイミングでdelete演算子によって削除し、オブジェクトの後始末やメモリの解放を行わなければなりません。ただしこの処理を漏れなくコーディングすることはプログラマにとっては負担であり、実際に削除されないままメモリリーク等の問題を起こしてしまうことも多々あります。
そこで動的に生成されポインタで参照されるオブジェクトに関しては、所謂スマートポインタで保持することが一般的となっています。スマートポインタで保持しておけば、ポインタ変数がスコープから外れ、オブジェクトがどこからも参照されなくなったタイミングで、自動的にオブジェクトの削除が行われます。C++ではこのためのスマートポインタとしてstd::unique_ptrやstd::shared_ptrを標準ライブラリに備えており、それらを使うことで動的に生成するオブジェクトの破棄をより確実にすることができます。
Choreonoidの実装でも標準ライブラリのスマートポインタは使用しているのですが、その一方で独自のオブジェクト型とそれに特化したスマートポインタも使用しています。従ってChoreonoidのプラグイン開発では、両者の違いについて把握し、両者を使い分けることが必要となってきます。
Referenced型のオブジェクトとref_ptr型のスマートポインタ¶
Choreonoidの実装における「独自のオブジェクト型とそれに特化したスマートポインタ」は、それぞれReferenced型とref_ptr型が対応します。これらはオブジェクト内蔵式の参照カウンタによって動的オブジェクトの共有を行う仕組みとなっています。
動的に生成するオブジェクトの保持にこれを利用する場合は、オブジェクトのクラスをReferenced型を継承して定義します。例えばObjectというクラスにこれを適用するとしたら、以下のようにします。
#include <cnoid/Referenced>
class Object : public cnoid::Referenced
{
public:
void function();
...
};
これでObjectクラスはReferenced型になります。このクラスは通常newで生成します。そして生成したポインタの保持にref_ptr型のスマートポインタを使用します。これは以下のように記述できます。
cnoid::ref_ptr<Object> object = new Object;
objectは通常のポインタ(生ポインタ)と同様に扱えます。例えば参照先オブジェクトのメンバ関数は以下のようにして実行できます。
object->fundtion();
あとは毎回ref_ptrで記述すると若干記述が複雑になるので、typedefで予めこのクラス専用のポインタ型として定義しておくことが多いです。このケースでは
typedef ref_ptr<Object> ObjectPtr;
としてtypedefしておき、
ObjectPtr object = new Object;
などと記述します。このようにtypedefする型名としては「クラス名 + Ptr」という名前にします。このルールとすることで、typedefした型名についてもスマートポインタ型であると認識できます。
スマートポインタなので、このポインタが破棄されるタイミングで参照しているオブジェクトも破棄されます。従ってオブジェクトに対して明示的にdeleteを実行する必要はありませんし、してはいけません。例えば
{
ObjectPtr object = new Object;
...
}
となっていると、このスコープの終了時点でobjectもdeleteされます。
もちろん実際の利用例はこのような単純なスコープであることはあまりありません。実際にはこのポインタが他のオブジェクトのメンバ変数として定義されていて、そこから参照されていたり、さらにそれが複数のオブジェクトに渡っていたりします。ref_ptrによる参照はそのように複数のポインタが同一のオブジェクトを重複して参照することが可能です。その場合、どのポインタからも参照されなくなった時点で、オブジェクトが破棄されます。
ここまでみてもらえば分かるように、あるオブジェクトを複数のポインタで共有して参照できるスマートポインタということで、これは基本的にはstd::shared_ptrと同様のものです。これを実現するための方法も基本的には同じで、どちらも参照カウンタを用いています。
weak_ref_ptr¶
shared_ptrでは弱参照を保持するweak_ptrも利用可能ですが、ref_ptrについても弱参照を保持するためのweak_ref_ptrを使用可能です。利用方法はweak_ptrとほぼ同じです。以下のようにref_ptrから生成できます。
ref_ptr<Object> object = new Object;
weak_ref_ptr<Object> wobject = object;
ref_ptrと同様に生ポインタからも生成できます。
weak_ref_ptr<Object> wobject = new Object;
weak_ptrと同様にlock関数でref_ptrを生成できます。
if(ref_ptr<Object> obj = wobject.lock()){
...
}
他にreset、expiredといった関数もweak_ptrと同様に使用できます。
独自クラスのReferenced化について¶
プラグインの実装において独自のクラスを定義する場合も、Referenced型を継承してref_ptrで参照することが可能です。 Referenced型である既存のクラスを継承する場合は、新たに定義するクラスも必然的にReferenced型となりますが、そうでない場合は必ずしもReferenced型とする必要はありません。 newを用いて動的に生成することが前提のクラスで、スマートポインタによる参照を行いたい場合は、Referenced型とする候補となります。 そのように使用する可能性があっても、自動変数やクラスのメンバ変数として直接定義して使用する可能性もある場合は、Referenced型にしてはいけません。そのようなクラスは、動的に生成して使用する場合のみ、shared_ptrで参照するようにします。