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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C++

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

Q&A

解決済

3回答

9588閲覧

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

kanade

総合スコア23

C++

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

0グッド

2クリップ

投稿2017/03/04 13:52

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

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

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

C++

1class SpriteList 2{ 3public: 4 void addSprite(Sprite* sprite); 5 Sprite* getSprite(int index); 6 7private: 8 std::vector<Sprite*> spriteList_; 9};

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

C++

1class SpriteList 2{ 3public: 4 void addSprite(std::shared_ptr<Sprite> sprite); 5 std::shared_ptr<Sprite> getSprite(int index); 6 7private: 8 std::vector<std::shared_ptr<Sprite>> spriteList_; 9};

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

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

C++

1class SpriteList 2{ 3public: 4 void addSprite(Sprite* sprite); 5 Sprite* getSprite(int index); 6 7private: 8 std::vector<std::shared_ptr<Sprite>> spriteList_; 9};

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

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

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

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

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

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

c++

1Sprite* getSprite(int index);

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

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

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

c++

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

以下はダメな例です。

c++

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

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

投稿2017/03/04 15:24

編集2017/03/05 01:52
hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kanade

2017/03/04 16:35

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

2017/03/06 16:03

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

0

ベストアンサー

こんにちは。

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

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

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

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

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

C++

1template< class Ch, class Tr, class Alloc> 2basic_format<Ch, Tr, Alloc>:: basic_format(const Ch* s, const std::locale & loc) 3 : style_(0), cur_arg_(0), num_args_(0), dumped_(false), 4 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/04 14:47

編集2017/03/04 14:52
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kanade

2017/03/04 16:22

ご回答ありがとうございます!そしていつもお世話になっております。 なるほど、C++ の特徴として文が続いてしまうことが理解できました。 boost... 私にはさっぱり読み解けません。。。ですが例としてお示しして下さった趣旨は理解できました。わざわざありがとうございます。 重ねてすみませんが、ご回答の中の【ところで】からの部分についてですが、 shared_ptr<> はできるだけ使わないほうが良いとおっしゃりましたが、それでは多くの場合、何を使うのが良いのでしょうか?生ポインタを使うのでしょうか?
Chironian

2017/03/04 17:40 編集

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

2017/03/06 16:01

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

0

しかしながら記述する量が増えてしまい(例えば 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/06 10:53

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kanade

2017/03/06 16:07

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問