システムプログラミング言語Rustは、所有権パラダイムを使用して、コンパイル時に、リソースを解放する必要があるランタイムのコストをゼロにすることを保証します( "Rust Book on Ownership" を参照)。 =)。
C++では、通常、スマートポインターを使用して、リソース割り当ての管理の複雑さを隠すという同じ目標を達成します。ただし、いくつかの違いがあります。
私の質問は、次の制約内でC++の所有権パラダイムをどのようにエミュレートできるかです。
編集:これまでのコメントから、次のように結論付けることができます。
Shared_ptrとweak_ptrの周りにラッパータイプを作成することで、独自のロールを作成できます。
コンパイル時のチェックではこれを行うことはできません。 C++型システムには、オブジェクトがスコープ外になる、移動される、または破棄される時期を推論する方法がありません。ましてや、これを型制約に変えることはできません。
実行時にアクティブな「借用」の数のカウンターを保持する_unique_ptr
_のバリアントを使用することができます。 get()
が生のポインターを返す代わりに、構築時にこのカウンターをインクリメントし、破棄時にデクリメントするスマートポインターを返します。カウントがゼロ以外のときに_unique_ptr
_が破棄された場合、少なくともどこかで誰かが何か間違ったことをしたことがわかります。
ただし、これは絶対確実なソリューションではありません。それを防ぐためにどれだけ懸命に努力しても、基になるオブジェクトへの生のポインターを取得する方法は常にあり、その生のポインターはスマートポインターと_unique_ptr
_よりも簡単に長持ちする可能性があるためゲームオーバーです。生のポインターを必要とするAPIと対話するために、生のポインターを取得する必要がある場合もあります。
さらに、所有権はポインタに関するものではありません。 Box
/_unique_ptr
_を使用すると、オブジェクトをヒープに割り当てることができますが、同じオブジェクトをスタック(または別のオブジェクト内、またはその他の場所)に配置する場合と比較して、所有権や存続期間などについては何も変わりません。本当に)。 C++のこのようなシステムから同じマイレージを得るには、_unique_ptr
_ sだけでなく、あらゆる場所のすべてのオブジェクトに対してこのような「借用カウント」ラッパーを作成する必要があります。そして、それはかなり非現実的です。
それでは、コンパイル時オプションをもう一度見てみましょう。 C++コンパイラは私たちを助けることはできませんが、おそらくリントは助けることができますか?理論的には、型システムの全ライフタイム部分を実装し、(独自のコードに加えて)使用するすべてのAPIにアノテーションを追加すると、機能する可能性があります。
ただし、プログラム全体で使用されるすべての関数に注釈が必要です。サードパーティライブラリのプライベートヘルパー機能を含みます。そして、ソースコードが利用できないもの。また、実装が複雑すぎてリンターが理解できない場合(Rustの経験から)、何かが安全である理由が微妙すぎて、ライフタイムの静的モデルで表現できない場合があります。コンパイラを支援するために少し異なる方法で記述されています)最後の2つでは、リンターはアノテーションが実際に正しいことを確認できないため、プログラマーを信頼することに戻ります。さらに、一部のAPI(つまり、APIの条件) Rustが使用するため、ライフタイムシステムでは実際にはうまく表現できません。
言い換えれば、これのための完全で実用的に有用なリンターは、失敗のリスクを伴う実質的な独自の研究になるでしょう。
たぶん、20%のコストで80%の利益を得る中間点があるかもしれませんが、あなたは厳しい保証が欲しいので(そして正直なところ、私もそれを望んでいます)、頑張ってください。 C++の既存の「グッドプラクティス」は、コンパイラの支援なしで、a Rustプログラマーのやり方を本質的に考えて(そして文書化して))、リスクを最小限に抑えるのにすでに大いに役立ちます。 C++の状態とそのエコシステムを考慮すると、それよりも大幅に改善されているかどうか。
tl; dr使用するだけRust ;-)
いくつかの厳密なコーディング規則を適用することで、Rustの利点のsomeを得ることができると思います(結局のところ、方法がないので、とにかくやらなければならないことです) 「テンプレートマジック」を使用して、コンパイラにnotコードをコンパイルするように指示します。notは「マジック」を使用します)。 .well ...kind of close、ただしシングルスレッドアプリケーションの場合のみ:
new
を直接使用しないでください。代わりに、make_unique
を使用してください。これは、ヒープに割り当てられたオブジェクトがRustのような方法で「所有」されるようにするための途中です。&&
)および/またはunique_ptr
sへのR値参照によって表されます。残念ながら、他の現存する参照がnoある場合にのみ、可変参照がシステム内のどこにでも存在できるというRustのルールを適用する方法は考えられません。
また、あらゆる種類の並列処理では、ライフタイムの処理を開始する必要があります。クロススレッドライフタイム管理(または共有メモリを使用したクロスプロセスライフタイム管理)を許可する唯一の方法は、独自の「 ptr-with-lifetime "ラッパー。ここでは、参照カウントが実際に重要になるため、これはshared_ptr
を使用して実装できます。ただし、参照カウントブロックには実際には2参照カウンターがあるため(1つはオブジェクトを指すすべてのshared_ptr
sに、もう1つはすべてのweak_ptr
sに)、それでも少し不必要なオーバーヘッドがあります。また、少しです... oddshared_ptr
シナリオでは、everybodyとshared_ptr
の所有権は「等しい」のに対し、「生涯借りる」シナリオでは、スレッドは1つだけです。/processは、実際にはメモリを「所有」する必要があります。
unique_ptr
の拡張バージョン(一意の所有者を強制するため)とobserver_ptr
の拡張バージョン(ダングリングポインターのNiceランタイム例外を取得するため、つまりunique_ptr
を介して維持された元のオブジェクトがスコープ外になった場合)を使用できます。 Trilinos パッケージは、この拡張されたobserver_ptr
を実装し、Ptr
と呼びます。ここにunique_ptr
の拡張バージョンを実装しました(私はそれをUniquePtr
と呼びます): https://github.com/certik/trilinos/pull/1
最後に、オブジェクトをスタックに割り当てたいが、安全な参照を渡すことができる場合は、Viewable
クラスを使用する必要があります。ここで私の最初の実装を参照してください: https:// github .com/certik/trilinos/pull/2
これにより、ポインタにRustと同じように、C++を使用できるようになります。ただし、Rustではコンパイル時エラーが発生し、C++では実行時例外が発生します。また、デバッグモードではランタイム例外のみが発生することに注意してください。リリースモードでは、クラスはこれらのチェックを行わないため、Rust(基本的に)と同じくらい高速です。生のポインタと同じくらい高速ですが、セグフォールトする可能性があります。したがって、テストスイート全体がデバッグモードで実行されていることを確認する必要があります。