Rust のスマート ポインター

C++ のスマート ポインターには、shared_ptr、unique_ptr、weak_ptr などが含まれることがわかっています。Rust では、スマート ポインターの意味をさらに理解できるようになります。
まずポインタを確認しましょう: その値はメモリ アドレスです。ポインタが指すメモリ アドレスにアクセスするには、ポインタを逆参照する必要があります。理論的には、任意のデータ型を逆参照できます。
スマート ポインター
スマート ポインターには、データへのポインターに加えて、追加の処理能力を提供するソース データもあります。
スマート ポインターとファット ポインターの違い: スマート ポインターはファット ポインターである必要がありますが、ファット ポインターは必ずしもスマート ポインターである必要はありません。

ここに画像の説明を挿入しますString (スマート ポインタ) はヒープ上の値の所有権を持ちますが、&str (ファット ポインタ) は所有権を持ちません。これが、Rust におけるスマート ポインタと通常のファット ポインタの違いです。

pub struct String { vec: Vec, } String の定義からすると明らかに構造体ですが、なぜスマートポインタになったのでしょうか?これは、String が昨日学んだばかりの Deref と DerefMut の 2 つの特性を実装しているためで、これにより逆参照時に &str が取得されます。


impl ops::Deref for String { type Target = str;

fn deref(&self) -> &str {
    unsafe { str::from_utf8_unchecked(&self.vec) }
}


データはヒープ上に割り当てられるため、割り当てられたリソースに応じて String もリサイクルする必要がありますString は内部で Vec を使用するため、ヒープ メモリを解放する Vec の機能に依存できます。
Rust では、リソースをリサイクルする必要があり、Deref/DerefMut/Drop の 3 つの特性を実装するデータ構造はすべてスマート ポインターであることがわかりました。

そうは言っても、次のものがスマート ポインターであることがわかります。

ヒープ上にメモリを割り当てるための Box と Vec、
参照カウントのための Rc と Arc
、およびその他の多くのデータ構造 PathBuf、Cow <'a B>、MutexGuard など。

これは、 Rust でヒープにメモリを割り当てる最も基本的な方法です。ヒープ メモリ割り当てを含む他のほとんどのデータ型は、Vec などの Box を通じて内部的に完了します。
まず、C がどのようにヒープ メモリを割り当てるかを見てみましょう。C では、メモリ割り当てを処理するために malloc/calloc/realloc/free が用意されていますが、最終的にメモリを安全に解放するために、私たちプログラマにとっては比較的大きな精神的負担がかかります。

C++ はこの問題を発見したため、ポインタがスコープを出るときにヒープ メモリを解放できる独自のスマート ポインタを作成し、ヒープ メモリの単一所有権を確保しました。これがBoxの前身です。
Box の定義には、C++ 参照を表す Unique があることがわかります。

ここに画像の説明を挿入します
ヒープにメモリを割り当てる Box には、実際にはデフォルトのジェネリック パラメータ A があり、Allocator 特性を満たす必要があります。これは実際にメモリ アロケータを指定し、デフォルトは Global です。もちろん、独自のメモリ アロケータに置き換えることもできます。
Allocator トレイトは多くのメソッドを提供します:
assign は主要なメソッドであり、C の malloc/calloc に対応してメモリを割り当てるために使用され、
deallocate は C の free に対応してメモリを解放するために使用され、
grow/shrink は割り当てられたヒープを拡張または縮小するために使用されます。メモリ。C の realloc に対応します。
Box の Drop 実装を見てみましょう。

#[stable(feature = “rust1”, because = “1.0.0”)]
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> { fn drop(&mut self) { // FIXME: 何もしない、ドロップは現在コンパイラによって実行されます。} }これは空の実装であることがわかります。通常の開発と同様に、最初にインターフェイスを決定し、次にそれぞれが独自のロジックを独自のサービスに実装します。これは私たちが話してきたインターフェイス指向プログラミングの考え方でしょうか?




カウ/ミューテックスガード

Cow<'a, B>
これは、仮想メモリ管理のコピーオンライトによく似たクローンオンライトを提供するために使用されるスマート ポインタです。**読み取りが多く書き込みが少ないシナリオで主に使用されます。
コード定義は次のとおりです:
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized { Borrowed(&'a B), Owned( ::Owned), }読み取り専用の借用が含まれています。呼び出し元が変更操作を行うために所有権が必要な場合、呼び出し元は借用したデータのクローンを作成します。よくわからないので詳細は省きます。




MutexGuard は、もう 1 つの興味深いタイプのスマート ポインターです。Deref を通じて優れたユーザー エクスペリエンスを提供するだけでなく、Drop 特性を使用して、終了時に使用されているメモリ以外のリソースが確実に解放されるようにします。

MutexGuard 構造体は、Mutex::lock を呼び出すときに生成されます:
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> { unsafe { self.inner.raw_lock(); MutexGuard::new(self) } }まずロックを取得してください。取得できない場合は待ちます。それを取得したら、MutexGuard への Mutex 参照を与えます。






MutexGuard のようなスマート ポインターにはさまざまな用途があります。たとえば、接続プールを作成するには、Drop 特性でチェックアウト接続をリサイクルし、それらを接続プールに戻すことができます。

RustにおけるBox、Rc、arc CELL RefCellの違い

Box は、C++ の unique_ptr に似た排他的所有権のスマート ポインターです。リソースはヒープ上に割り当てられ、Deref と Drop に依存してヒープ上のリソースを管理します。ランタイム オーバーヘッドはありません。Rc は、共有所有権のスマート ポインターです。C++ のshared_ptr リソースに似
ます。ヒープ上に割り当てられ、Deref と Drop に依存してヒープ上のリソースを管理し、参照カウント アルゴリズムを使用します。
Arc はスレッドセーフな共有所有権スマート ポインターであり、C++ のshared_ptr + ミューテックス リソースは Deref に依存してヒープ上に割り当てられます。参照カウント アルゴリズムを使用してヒープ上のリソースを管理するには、Drop を使用します。

Cell と RefCell は内部可変性を実現できます:
1.Cell は Copy 値をラップし、借用チェックはありません;
2.RefCell は任意のタイプの値をラップし、実行時借用チェックがあり、それぞれ、不変を提供する、boror またはborrow_mut でロックする必要があります。または可変参照;
3. Cell も RefCell もスレッドセーフではありません。
Cell と比較して、RefCell はパッケージング オブジェクトの参照カウントを内部的に維持します。RefCell の借用によって不変参照が取得されると、内部参照カウントが 1 増加します。取得された参照がスコープ外に出ると、内部参照カウントが 1 減少します。Cell は参照カウントを導入していないため、Cell は T を満たす必要があります
。 : コピー。
impl<T: Copy> Cell {}
Cell の場合、get によって取得されるのは元のオブジェクトのコピーですが、set によって古いオブジェクトが新しいオブジェクトに置き換えられます。

use std::cell::Cell;

#[derive(Debug)]
struct Person {
    
    
    name: String,
    age: Cell<usize>,
}

fn main() {
    
    
    let person = Person {
    
    
        name: "XiaoMing".to_string(),
        age: Cell::new(18),
    };

    let p = &person;
    p.age.set(20);
    // Person {
    
     name: "XiaoMing", age: Cell {
    
     value: 20 } }
    println!("{:?}", p);
}
use std::cell::RefCell;

#[derive(Debug)]
struct Person {
    
    
    name: String,
    age: RefCell<usize>,
}

fn main() {
    
    
    let person = Person {
    
    
        name: "XiaoMing".to_string(),
        age: RefCell::new(18),
    };

    let p = &person;
    *p.age.borrow_mut() = 20;
    // Person {
    
     name: "XiaoMing", age: Cell {
    
     value: 20 } }
    println!("{:?}", p);
}

cell と refcell の役割は、p は不変の借用ですが、cell と refcell を使用して p の内部値を変更できることであることがわかります。違いは、セルが get を通じて元のオブジェクトのコピーを取得することです。get メソッドはビットごとに直接コピーするため、Copy を実装する型や小さな構造体に適しています。
refcell は、借用チェックを通じて可変参照を返します。Copy を実装していない型や大きな構造体に適しており、Cell が使いにくい場合に使用されます。

おすすめ

転載: blog.csdn.net/weixin_53344209/article/details/130053246