c++11-スマートポインタと参照カウント

25075 ワード

一、本節の内容
このセクションの内容は次のとおりです.
標準ライブラリの拡張:スマートポインタと参照カウント
  • RAIIと参照カウント
  • std::shared_ptr
  • std::unique_ptr
  • std::weak_ptr


  • 二、RAIIと引用数Objective-C/Swiftを知っているプログラマーは、参照カウントの概念を知っているはずです.リファレンスカウントというカウントは、メモリの漏洩を防ぐために発生します.基本的な考え方は、動的に割り当てられたオブジェクトに対して参照カウントを行い、同じオブジェクトへの参照を1回増やすたびに参照オブジェクトの参照カウントが1回増加し、参照を削除するたびに参照カウントが1つ減少し、1つのオブジェクトの参照カウントがゼロに減少すると、指向するスタックメモリが自動的に削除されることです.
    従来のC++では、リソースを手動で解放するのは、必ずしもベストプラクティスではありません.私たちは資源を解放することを忘れて流出する可能性が高いからです.したがって、通常の方法は、オブジェクトにとって、関数を構築するときに空間を申請し、構造関数(役割ドメインから離れたときに呼び出される)を解析するときに空間を解放することです.つまり、RAIIリソース取得、すなわち初期化技術です.
    何事にも例外があり、オブジェクトをフリーストレージに割り当てる必要があります.従来のC++では、newdeleteを使用してリソースを解放するしかありません.C++11はスマートポインタの概念を導入し,参照カウントの考え方を用いて,プログラマが手動でメモリを解放する必要がなくなるようにした.これらのスマートポインタは、std::shared_ptr/std::unique_ptr/std::weak_ptrを含み、ヘッダファイルを含む必要がある.
    注意:リファレンスカウントはゴミ回収ではなく、リファレンス技術は使用されなくなったオブジェクトをできるだけ早く回収することができ、同時に回収の過程で成長時間の待ち時間を作ることもなく、資源のライフサイクルをより明確に示すことができる.
    三、std::shared_ptr std::shared_ptrは、複数のshared_ptrが共に1つのオブジェクトを指していることを記録することができ、表示された呼び出しdeleteを除去し、参照カウントがゼロになるとオブジェクトを自動的に削除するスマートポインタである.
    しかし、std::shared_ptrを使用するには、newを使用して呼び出す必要があるため、コードはある程度非対称になる.std::make_sharedは、表示の使用を排除するために使用されるnewであるため、std::make_sharedは、入力パラメータの作成中のオブジェクトを割り当て、このオブジェクトタイプのstd::shared_ptrポインタを返す.例:
    #include 
    #include 
    
    void foo(std::shared_ptr<int> i)
    {
        (*i)++;
    }
    int main()
    {
        // auto pointer = new int(10); //   ,        
        //       std::shared_ptr
        auto pointer = std::make_shared<int>(10);
        foo(pointer);
        std::cout << *pointer << std::endl; // 11
    
        //       ,shared_ptr     ,      
        return 0;
    }
    
    std::shared_ptrは、get()によって元のポインタを取得し、reset()によって参照カウントを減少させ、get_count()によってオブジェクトの参照カウントを表示することができる.例:
    auto pointer = std::make_shared<int>(10);
    auto pointer2 = pointer;    //     +1
    auto pointer3 = pointer;    //     +1
    int *p = pointer.get();             //           
    
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 3
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 3
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 3
    
    pointer2.reset();
    std::cout << "reset pointer2:" << std::endl;
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 2
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 0, pointer2   reset
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 2
    
    pointer3.reset();
    std::cout << "reset pointer3:" << std::endl;
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 1
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 0
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 0, pointer3   reset
    

    四、std::unique_ptr std::unique_ptrは、他のスマートポインタが同じオブジェクトを共有することを禁止し、コードのセキュリティを保証する独自のスマートポインタです.
    std::unique_ptr<int> pointer = std::make_unique<int>(10);   // make_unique   C++14   
    std::unique_ptr<int> pointer2 = pointer;    //   
    

    make_uniqueは複雑ではありません.C++11はstd::make_を提供していません.Unique、自己実現可能:
    template<typename T, typename ...Args>
    std::unique_ptr make_unique( Args&& ...args ) {
        return std::unique_ptr( new T( std::forward(args)... ) );
    }
    

    なぜ提供されなかったのかについては、C++標準委員会のHerb Sutter議長がブログで「忘れられた」ためだと述べた.
    独占である以上、言い換えれば複製できない.しかし、std::moveを使用して、他のunique_ptrに移行することができます.例えば、
    #include 
    #include 
    
    struct Foo {
        Foo()      { std::cout << "Foo::Foo" << std::endl;  }
        ~Foo()     { std::cout << "Foo::~Foo" << std::endl; }
        void foo() { std::cout << "Foo::foo" << std::endl;  }
    };
    
    void f(const Foo &) {
        std::cout << "f(const Foo&)" << std::endl;
    }
    
    int main() {
        std::unique_ptr p1(std::make_unique());
    
        // p1   ,   
        if (p1) p1->foo();
        {
            std::unique_ptr p2(std::move(p1));
    
            // p2   ,   
            f(*p2);
    
            // p2   ,   
            if(p2) p2->foo();
    
            // p1   ,    
            if(p1) p1->foo();
    
            p1 = std::move(p2);
    
            // p2   ,    
            if(p2) p2->foo();
            std::cout << "p2    " << std::endl;
        }
        // p1   ,   
        if (p1) p1->foo();
    
        // Foo               
    }
    

    五、std::weak_ptr std::shared_ptrをよく考えてみると、依然として資源が解放できない問題があることがわかります.次の例を見てください.
    #include 
    #include 
    
    class A;
    class B;
    
    class A {
    public:
        std::shared_ptr pointer;
        ~A() {
            std::cout << "A    " << std::endl;
        }
    };
    class B {
    public:
        std::shared_ptrpointer;
    ~B() {
    std::cout<「Bが  される」<<std::endl;
    }
    };
    int main() {
    std::shared_ptra = std::make_shared();
    std::shared_ptr b = std::make_shared();
    a->pointer = b;
    b->pointer = a;
    return 0;
    }

    実行結果はA,Bともに破棄されない.これは、a,b内部のpointerが同時にa,bを参照しているためであり、a,bの参照カウントはいずれも2となり、作用域を離れるとa,bのスマートポインタが解析されるが、スマートによってこの領域の参照カウントが1減少し、これにより、a,bオブジェクトが指すメモリ領域参照カウントがゼロではなく、外部ではこの領域を見つけることができなくなり、図に示すようにメモリ漏れが発生します.
    この問題を解決する方法は、弱参照ポインタstd::weak_ptrstd::weak_ptrを使用して弱参照である(比較的std::shared_ptrは強参照である).弱参照は参照カウントの増加を引き起こすことはなく、弱参照を使用する場合、最終的な解放プロセスは下図のようになる.
    上図では、最後のステップはBしか残っていませんが、Bにはスマートポインタが参照されていないため、このメモリリソースも解放されます.std::weak_ptrには*演算子と->演算子がないので、リソースを操作することはできません.その唯一の役割は、std::shared_ptrが存在するかどうかを確認することです.expired()方法は、リソースが解放されていない場合、trueを返します.そうでなければfalseを返します.
    正しいコードは次のとおりです.
    #include 
    #include 
    
    class A;
    class B;
    
    class A {
    public:
        // A   B          weak_ptr
        std::weak_ptr pointer;
        ~A() {
            std::cout << "A    " << std::endl;
        }
    };
    class B {
    public:
        std::shared_ptrpointer;
    ~B() {
    std::cout<「Bが  される」<<std::endl;
    }
    };
    int main() {
    std::shared_ptra = std::make_shared();
    std::shared_ptr b = std::make_shared();
    a->pointer = b;
    b->pointer = a;
    return 0;
    }

    まとめ
    スマートポインタという技術は珍しくなく、多くの言語でよく見られる技術であり、C++1 xはこの技術を導入し、new/deleteの乱用をある程度解消し、より成熟したプログラミングモデルである.