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

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

新規登録して質問してみよう
ただいま回答率
85.35%
ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

C++

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

Q&A

解決済

2回答

1976閲覧

shared_ptrを用いた状態管理

gRivXOzYyYyDl1W

総合スコア20

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

C++

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

0グッド

0クリップ

投稿2020/10/17 19:09

C++のスマートポインタ使った状態管理についての質問です。

自身のポインタを戻り値としたClass* Class::Update()関数で状態を管理するプログラムをshared_ptr<Class>を用いて書きたいのですが、
shared_ptr<Class> class class.reset(class->Update())という書き方だとアクセス違反となってしまいます。
この原因が分かりません。スマートポインタの仕様でclass変数が解放されているのでしょうか?
どうかご教授お願い致します。
下記の例のプログラムは、CSMA/CAのシミュレーションを作っています。
以下 struct Channelとstruct ChannelStateを使ったプログラム例(一部割合)

Channel.h

/*通信路クラス*/ struct Channel { public: static shared_ptr<Channel> channel; /*シングルトン*/ static list<shared_ptr<PassChannel>> TerminalList; /*端末情報*/ static list<shared_ptr<Packet>> packets; /*通信路に送られたパケット*/ Channel(); void Update(); private: shared_ptr<ChannelState> state; //通信路の状態 };

Channel.cpp

void Channel::Update() { state.reset(state->Update()); //アクセス違反 return; }

ChannelState.h

/*通信路状態の基底クラス*/ struct ChannelState { public: ChannelState(); virtual ChannelState* Update(); }; /*パケット送信可能状態*/ struct Idle:public ChannelState { public: Idle(); ChannelState* Update() override; }; /*パケット送信中状態*/ struct Busy:public ChannelState { public: Busy(); ChannelState* Update() override; private: shared_ptr<Packet> packet; int Counter; }; /*衝突発生*/ struct Collision :public ChannelState { public: Collision(); ChannelState* Update() override; };

ChannelState.cpp

ChannelState* Idle::Update() { if ((int)Channel::packets.size()==1) { return new Busy(); } else if ((int)Channel::packets.size()>1) { return new Collision(); } return this; } ChannelState* Busy::Update() { Counter++; if (Counter == packet->transferTime) { list<shared_ptr<PassChannel>>::iterator itr; itr = Channel::TerminalList.begin(); while ((*itr)->GetID() != packet->distination) { itr++; } (*itr)->ReceivePacket(packet.get()); return new Idle(true); } return this; } ChannelState* Collision::Update() { if ((int)Channel::packets.size()==1) { return new Busy(); } else if (Channel::packets.size() >= 1) { return new Collision(); } return this; }

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

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

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

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

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

guest

回答2

0

ベストアンサー

生ポインタから std::shared_ptr を作った時点が共有状態の管理の開始であると考えるとわかりやすいです。

たとえば以下のようなコードは問題があるコードだというのがわかるでしょうか。

#include <memory> #include <iostream> int main(void) { int *p = new int(1); std::shared_ptr<int> sp1(p); std::shared_ptr<int> sp2(p); std::cout << sp1.use_count() << std::endl; std::cout << sp2.use_count() << std::endl; return 0; }

sp1 を作った時に「よし、 p の管理を開始するぞ!」となっていて、それとは別に sp2 を作った時にも「よし、 p の管理を開始するぞ!」となるので sp1sp2 はお互いに同じものを管理していることを知らないのです。 ですから一方の管理においてカウンタが 0 になってオブジェクトを解体するともう一方を通じてオブジェクトにアクセスすると違反になってしまいます。

同様のことが reset についても言えます。 std::shared_ptr オブジェクトが既に管理対象としているポインタと新たに与えるポインタが同一である場合には同じポインタについての管理が二重になってしまいます。

解決方法としては以下のみっつが思いつきます。

  • 専用のポインタ管理機構を作る
  • Update が常に (新しいオブジェクトを生成して) 新しいポインタを返す
  • Update が以前と同じポインタを返してきた場合には reset しない

投稿2020/10/18 03:57

SaitoAtsushi

総合スコア5684

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

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

gRivXOzYyYyDl1W

2020/10/18 16:03

ご回答ありがとうございます。 shared_ptrの仕様を誤解していました。 おかげさまで無事解決しました。 ありがとうございました。
guest

0

virtual ChannelState* Update() は、自分自身か、新しいChannelStateクラスのインスタンスを返すメソッド。で合っていますか?

前提ですが、なるべく左辺値参照とunique_ptrを使うべきで、shared_ptrは避けるべきです。
そのリソースを開放する義務のある人が誰なのか、わかりにくくなる為です。
(shared_ptrしか見当たらないので、念の為)

アクセス違反を起こしている箇所ですが、思い浮かんだのは2つです

・別の場所の実装でリリースされて state が空になっている(この場合質問文から判断できないです)
・メモリリークを起こしている(この場合アクセス違反にはならないと思いますが)

メモリリークを起こしている、についてのみ説明します。
該当箇所を以下のように分解してみます。

auto p = state.get(); state.reset(); auto new_p = p->Update(); state.reset(new_p);

p->Update() で新しいインスタンスを返すとき、古いインスタンス(p)は誰にも解放されず残り続けることが分かると思います。resetは所有権を放棄するだけで、解放しない為です。

解決策としては、Collision::Updateが常に新しいインスタンスを返すようにすることです。
例えば、以下のようにする、等です。

std::unique_ptr<ChannelState> Idle::Update() { if ((int)Channel::packets.size()==1) { return std::make_unique<Busy>(); } else if ((int)Channel::packets.size()>1) { return std::make_unique<Collision>(); } return std::make_unique<Idle>(*this); }

投稿2020/10/18 03:22

maai

総合スコア463

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

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

gRivXOzYyYyDl1W

2020/10/18 16:04

ご回答ありがとうございます。 shared_ptrの仕様を誤解していました。 今後はunique_ptrを意識してプログラミングします。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問