none
スマートポインタの利用 RRS feed

  • 質問

  • いつもお世話になっております。

    別スレッドにてアドバイスをいただき、スマートポインタについて理解を深めようと学習しております。


    学習をしていく過程で、疑問が浮かんできたので質問させてください。


    以下のような (1) (2) のコードは、どちらも同じことが実現出来ていると思うのですが、スマートポインタの使い方として正解はあるのでしょうか?

    // (1) 関数にスマートポインタの参照を渡す
    void func(const std::shared_ptr<hoge>& arg)
    {
    	// arg に対する何らかの処理
    }
    
    int main()
    {
    	std::shared_ptr<hoge> sptr = std::make_shared<hoge>();
    	func(uptr);
    }
    
    
    // (2) 関数にはスマートポインタではなく変数の参照を渡す
    void func(hoge& arg)
    {
    	// arg に対する何らかの処理
    }
    
    int main()
    {
    	std::unique_ptr<hoge> uptr = std::make_unique<hoge>();
    	func(*uptr);
    }


    [環境]

    OS : Windows 7 x64

    IDE : Visual Studio 2013 / 2015

    言語:C++

    2018年3月21日 12:48

回答

  • 「hoge* と書いていた部分を std::shared_ptr<hoge> に置き換えることができる」という基本が理解できていれば参照であっても同じです。

    void func(const std::shared_ptr<hoge>& arg) を void func(const hoge*& arg) に置き換えて関数として意図した内容かを判断してください。意図通りであれば問題ありませんし、 hoge*& をなんじゃらほいと感じるのであれば間違っているのでしょう。
    # hoge*& 自体はあまり一般的ではありませんが、とはいえ全く間違いでもなく必要となる場面もあるため、関数の中身で決まります。

    • 回答としてマーク siita 2018年3月22日 10:00
    2018年3月21日 14:52
  • スマートポインターはポインターの一種です。hoge* と書いていた部分を std::shared_ptr<hoge> に置き換えることができます。

    // hoge*バージョン
    void func(hoge* arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = new hoge();
    	func(uptr);
    }
    
    // shared_ptrバージョン
    void func(std::shared_ptr<hoge> arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = std::make_shared<hoge>();
    	func(uptr);
    }
    

    std::shared_ptr<hoge>はhoge*と同じく自由にコピーできますが、コピーの際に参照カウントがされており、参照がすべてなくなったタイミングで自動的にデストラクタが実行されます。

    unique_ptrも基本的な使い方はshared_ptrと同じですが、所有権の考え方が異なります。上記例ではfunc関数のarg、main関数のuptrの2変数が同時に所有権を持っていますが、unique_ptrではこれが許されません。mainからfuncを呼ぶ際に移動を行い所有権を放棄する必要があります(放棄しない場合はコンパイルエラーとなります)。

    // unique_ptrバージョン
    void func(std::unique_ptr<hoge> arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = std::make_unique<hoge>();
    	func(std::move(uptr));
    }
    • 回答としてマーク siita 2018年3月22日 10:00
    2018年3月21日 13:49

すべての返信

  • スマートポインターはポインターの一種です。hoge* と書いていた部分を std::shared_ptr<hoge> に置き換えることができます。

    // hoge*バージョン
    void func(hoge* arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = new hoge();
    	func(uptr);
    }
    
    // shared_ptrバージョン
    void func(std::shared_ptr<hoge> arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = std::make_shared<hoge>();
    	func(uptr);
    }
    

    std::shared_ptr<hoge>はhoge*と同じく自由にコピーできますが、コピーの際に参照カウントがされており、参照がすべてなくなったタイミングで自動的にデストラクタが実行されます。

    unique_ptrも基本的な使い方はshared_ptrと同じですが、所有権の考え方が異なります。上記例ではfunc関数のarg、main関数のuptrの2変数が同時に所有権を持っていますが、unique_ptrではこれが許されません。mainからfuncを呼ぶ際に移動を行い所有権を放棄する必要があります(放棄しない場合はコンパイルエラーとなります)。

    // unique_ptrバージョン
    void func(std::unique_ptr<hoge> arg) {
    	// arg に対する何らかの処理
    }
    
    int main() {
    	auto uptr = std::make_unique<hoge>();
    	func(std::move(uptr));
    }
    • 回答としてマーク siita 2018年3月22日 10:00
    2018年3月21日 13:49
  • 佐祐理さん、いつも迅速なご回答ありがとうございます。

    ご回答いただいた内容は全て理解しております。(以下のようになると思います)

    // (1) shared_ptr参照渡しバージョン void func(const std::shared_ptr<hoge>& arg) { // arg に対する何らかの処理 (参照カウンタ1) } int main() { std::shared_ptr<hoge> sptr = std::make_shared<hoge>(); func(uptr); // ここでは sptr の所有権があるため、sptr への操作が可能 } // (2) shared_ptr値渡しバージョン void func(std::shared_ptr<hoge> arg) { // arg に対する何らかの処理 (参照カウンタ2)

    } // ここで参照カウンタ1になる int main() { auto sptr = std::make_shared<hoge>(); func(sptr); // ここでは sptr の所有権があるため、sptr への操作が可能 } // (3) unique_ptr で管理している対象のデータの参照バージョン void func(hoge& arg) { // arg に対する何らかの処理 } int main() { std::unique_ptr<hoge> uptr = std::make_unique<hoge>(); func(*uptr); // ここでは uptr の所有権があるため、uptr への操作が可能 } // (4) unique_ptrバージョン void func(std::unique_ptr<hoge> arg) { // arg に対する何らかの処理

    } // 参照がなくなり、解放される int main() { auto uptr = std::make_unique<hoge>(); func(std::move(uptr)); // ここでは uptr の所有権がない(解放済み)ため、uptr への操作はできない }


    質問の意図がうまく伝わらなかったようですので、改めて質問させていただきます。


    今回質問したかった内容としては、(1) と (3) のケースのように、関数に渡す際に、(1) shared_ptr の参照を渡すのと (3) unique_ptr で管理している対象のデータの参照を渡すのはどちらがスマートポインタの使い方として正しいのか?または、どちらでもよいのか?といったことになります。

    (3) で問題ないのであれば、既存の実装で参照渡しとなっている関数については書き換える必要はなく、関数の呼び出し側の実装を new/delete から unique_ptr に変更するだけになると考えています。

    • 編集済み siita 2018年3月21日 14:26
    2018年3月21日 14:23
  • 「hoge* と書いていた部分を std::shared_ptr<hoge> に置き換えることができる」という基本が理解できていれば参照であっても同じです。

    void func(const std::shared_ptr<hoge>& arg) を void func(const hoge*& arg) に置き換えて関数として意図した内容かを判断してください。意図通りであれば問題ありませんし、 hoge*& をなんじゃらほいと感じるのであれば間違っているのでしょう。
    # hoge*& 自体はあまり一般的ではありませんが、とはいえ全く間違いでもなく必要となる場面もあるため、関数の中身で決まります。

    • 回答としてマーク siita 2018年3月22日 10:00
    2018年3月21日 14:52
  • 佐祐理さん、ご回答ありがとうございます。

    つまりは、実装が意図したものになっていれば、あとは実装者の好きに実装してよいという事ですね。

    (なんだか当たり前のことを言っているような気がしますが…)
    2018年3月22日 10:00
  • そうですね、ダメなパターンではコンパイルエラーとなることがスマートポインターの利点です。ですので、(1)、(3)どちらがいいかの観点となると関数内の処理に依存してきます。
    # 当たり前なことしか書けず申し訳ありません…(’’;
    2018年3月22日 13:17