質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.52%

  • C++

    3444questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

スマートポインタについて質問です。

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,289

kanade

score 17

こんばんは、趣味で C++ でゲームプログラミングをしている者です。
現在 C++ でポインタを扱う際には生のポインタを扱うことは相応しくないと聞き、unique_ptr, shared_ptr, weak_ptr などのスマートポインタについて学習しました。
スマートポインタにより delete を書かなくてよくなりメモリリークの心配が減りました。

しかしながら記述する量が増えてしまい(例えば Sprite* sprite ではなく std::shared_ptr<Sprite> sprite)、コードが読みづらくなってしまいました。

例えば生のポインタを使った場合、

class SpriteList
{
public:
    void addSprite(Sprite* sprite);
    Sprite* getSprite(int index);

private:
    std::vector<Sprite*> spriteList_;
};

程度の記述で十分なのに、スマートポインタを使った場合には、

class SpriteList
{
public:
    void addSprite(std::shared_ptr<Sprite> sprite);
    std::shared_ptr<Sprite> getSprite(int index);

private:
    std::vector<std::shared_ptr<Sprite>> spriteList_;
};


と、長い記述が必要になってしまいました。
using などを使えば短く書くことはできるとは思うのですが、これは仕方のないことなのでしょうか。

スマートポインタを活用して書かれたコードを見たことがないため、いまいち正しい使い方が分かっておりません。
例えばスマートポインタに対する Getter/Setter の引数や戻り値にもスマートポインタを使うべきなのでしょうか?
上にお示ししたスマートポインタのコードではそのようにしたのですが、例えば、

class SpriteList
{
public:
    void addSprite(Sprite* sprite);
    Sprite* getSprite(int index);

private:
    std::vector<std::shared_ptr<Sprite>> spriteList_;
};


のようにしてあげれば冗長な記述が不要となる気もしました。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+2

こんにちは。

しかしながら記述する量が増えてしまい(例えば Sprite* sprite ではなく std::shared_ptr<Sprite> sprite)、コードが読みづらくなってしまいました。

そうなんですよね。C++はめったやたらと文が長くなる傾向があるので辛いですね。
そして、そのエラー・メッセージときたら数KBytes続くことも結構ありますし、地獄です。

でも、諦めるのが吉と思います。
1文をなるべく1行に収めて見やすくしたいのですが、C++ではなかなか叶いません。
寧ろ1文を如何に読みやすく複数行に分割するのか考えた方が建設的と思いますよ。

boostと言う標準ライブラリに一番近いところにあるライブラリがありますが、こんな感じです。

以下は1文です。クラクラしますが、コンストラクタの宣言部分です。

template< class Ch, class Tr, class Alloc>
basic_format<Ch, Tr, Alloc>:: basic_format(const Ch* s, const std::locale & loc)
    : style_(0), cur_arg_(0), num_args_(0), dumped_(false),
      exceptions_(io::all_error_bits), loc_(loc)

もっと短く書けるようになるとよいのですが、高度なメタ・プログラミングができるのでイカンともしがたいと思います。メタ・プログラムするとコンパイル時に処理してしまうので、実行時間が事実上0になるのですよ。なので、こんなに使いにくくても、使える時は使いたくなります。

後、usingやマクロ等を使って、短くして可読性を上げる工夫はやった方が良いと思います。


【ところで】
生ポインタをそのままstd::shared_ptr<>へ置き換えるのは適切ではないです。
無駄に重たくなりますし、std::shared_ptr<>は一歩間違うと循環参照して結局リークしますし。

私はstd::shared_ptr<>はちょっと重いし、相互依存問題があるので、できるだけ使わな方が良いと思います。
グローバル変数やgoto文、public変数はなるべく使わない方が良いと言われますが、それらと同じような意味でです。(必要な時だけ使いましょう。)

のようにしてあげれば冗長な記述が不要となる気もしました。

std::unique_ptr<>を使うことができるとき、その記述は好ましいと私は思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/05 01:22

    ご回答ありがとうございます!そしていつもお世話になっております。
    なるほど、C++ の特徴として文が続いてしまうことが理解できました。
    boost... 私にはさっぱり読み解けません。。。ですが例としてお示しして下さった趣旨は理解できました。わざわざありがとうございます。

    重ねてすみませんが、ご回答の中の【ところで】からの部分についてですが、 shared_ptr<> はできるだけ使わないほうが良いとおっしゃりましたが、それでは多くの場合、何を使うのが良いのでしょうか?生ポインタを使うのでしょうか?

    キャンセル

  • 2017/03/05 02:39 編集

    オブジェクトの所有権を管理する(オブジェクトを破棄する)ポインタのみstd::unique_ptr<>を使うのが良いと思います。それ以外のポインタは可能ならば参照、無理なら生ポインタが好ましいと思います。

    解放済領域に対する保護が全くない生ポインタが心配な時は、オブジェクトの所有権を管理する(オブジェクトを破棄する)ポインタについてのみstd::shared_ptr<>で、それ以外のポインタをstd::weak_ptr<>でも良いと思います。それなりに重くはなりますがより安全ですね。
    weak_ptr<>のlock()はshard_ptr<>をコピーするので参照カウンタの操作もします。生ポインタやunique_ptr<>でオブジェクト・アクセスするのに比べると結構重いです。

    std::shared_ptr<>は重く、オブジェクトの共有機能を安易に使うとリソース・リークするので慎重に使いましょうということです。

    キャンセル

  • 2017/03/07 01:01

    なるほど。。。詳しくありがとうございます。
    確かにshared_ptr<>よりもunique_ptr<>のほうが安全ですよね。
    スマートポインタに関する勉強不足を痛感しました。
    ありがとうございました。

    キャンセル

+2

using などを使えば短く書くことはできるとは思うのですが、これは仕方のないことなのでしょうか。

これはしょうがいないですね。usingを使いましょう。

例えばスマートポインタに対する Getter/Setter の引数や戻り値にもスマートポインタを使うべきなのでしょうか?

正当な理由がない限りは使うべきです。

のようにしてあげれば冗長な記述が不要となる気もしました。

Sprite* getSprite(int index);


例えば上記で受け取ったポインタをSpriteListのインスタンスが破棄されたあとに
使用すると不正なメモリにアクセスすることになります。
SpriteListクラスを使用するプログラマは上記を常に意識しなければいけません。
shared_ptrもしくはweak_ptrで返却してあげれば、その心配がありません。

追記
weak_ptrについてコメントをいただいたので追記します。

weak_ptrはsahred_ptrが必ず取得できるわけではありません。
ですので、通常は以下のように使います。

auto shared = weakptr.lock();//shared_ptr<>を取得
if(shared){//使えるか確認
    shared->render();//使う。
                     //shared_ptrで参照カウントがカウントアップされているので安全に使える。
}

以下はダメな例です。

if(weakptr.lock()){
    weakptr.lock()->render();//危ない!!
}

最初のロックと2回目のロックの間にweakptrで監視しているshared_ptrが破棄されている場合があります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/05 01:35

    ご回答ありがとうございます!
    すべての質問にお答えくださりありがとうございます。
    もやもやとしていた部分がハッキリとしました。
    ご回答の最後の部分にshared_ptr若しくはweak_ptrで返却してあげれば良いとあります。理由にも大変納得ができました。少し質問がずれてしまい恐縮なのですが、weak_ptrでポインタにアクセスする場合shared_ptrのように->演算子ではなくlock()でshared_ptrを取得してからアクセスできるかと思いますが、様々なサイトでlock()について、まず、たとえば shared_ptr<Sprite> sharedPtr = weakPtr.lock(); とし、次の行でsharedPtr->render();としていましたが、これをまとめて、weakPtr.lock()->render();としてはいけないのでしょうか?可読性のためにこのように書かれているのか、或いは訳あって書かれているのか、初心者には分からず困っています。

    キャンセル

  • 2017/03/07 01:03

    lock()についてご丁寧に教えてくださりありがとうございます!
    lock()の意義がよくわかり大変助かりました。

    キャンセル

+2

しかしながら記述する量が増えてしまい(例えば Sprite* sprite ではなく std::shared_ptr<Sprite> sprite)、コードが読みづらくなってしまいました。 

他人(未来の自分も含む)がソースコードから読み取れる情報量と、ソースコードの見た目とのトレードオフ問題ですが、慣れてしまえばそこまで読みづらく無いと思います :P

using などを使えば短く書くことはできるとは思うのですが、これは仕方のないことなのでしょうか。
スマートポインタを活用して書かれたコードを見たことがないため、いまいち正しい使い方が分かっておりません。

基本的には、プロジェクト単位の判断でしょう。

例えば、Android(OS本体)のC++コードでは、std::shared_ptr<T>/std::weak_ptr<T>相当の独自テンプレートsp<T>/wp<T>を定義・利用しています。賛否あるでしょうが、コードが短くなって読みやすいと思う人もいれば、暗号めいた名前で意味が取りづらいという人もいると思います。ソースコード上で頻出するスマートポインタ型であれば、typedef std::shared_ptr<FooBarClass> FooBarPtr;のような別名(alias)を付けておくこともあります。


例えばスマートポインタに対する Getter/Setter の引数や戻り値にもスマートポインタを使うべきなのでしょうか?

クラス外部設計(セマンティクス=意味づけ)に依存しますが、まずはポインタが指すオブジェクトの「所有権(ownership)」の観点を整理すべきと思います。

  • unique_ptrは所有権を安全に排他管理します。unique_ptrが所有権を放棄するとき、オブジェクトは自動的に破棄されます。
  • shared_ptrは所有権を安全に共有管理します。全てのshared_ptrが所有権を放棄するとき、オブジェクトは自動的に破棄されます。
  • weak_ptrは所有を行わず、共有管理(shared_ptr)先を安全に参照します。共有されるオブジェクトの有無を安全に確認できます。
  • 生ポインタは上記いずれも行えますが、一切の安全機構が存在しません。プログラマがミスすると、メモリリークやメモリ破壊等の厄介なバグが発生します。

上記を前提としてGetter/Setterの戻り値/引数型を選択することになります。例えば、shared_ptr型クラスメンバでオブジェクトを保持するなら、Setterはshared_ptr型(または生ポインタ)を引数に撮り、Getterはshared_ptr型またはweak_ptr型(または生ポインタ)を返す案がありえます。いずれもクラス外部設計次第です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/07 01:07

    ご回答ありがとうございます!
    時と場合に応じて最適な選択をする必要がありそうですね。
    まだまだ勉強不足で難しいですが色々なコードを読んで勉強していきたいと思います。
    ありがとうございました。

    キャンセル

  • 2017/03/07 11:08

    英語記事ですが、Herb Stutter氏(書籍「Exceptional C++」著者)のブログ解説が色々参考になると思います。
    https://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/
    https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C++

    3444questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。