読者です 読者をやめる 読者になる 読者になる

イテレータを使う時、破壊されないようにしよう

C++iteratorを使う時にありがちな不可解バグとして「イテレータを走査中にイテレータが破壊される操作を行う事で不可解な挙動を示す」ことがあげられる。
例えばvectoriteratorを走査中には追加や削除を行ってはいけない。
これはポインタを利用したvectorを使っていてクラス構造が複雑になると気付かないうちに発生したりする。

例えば、

class IMessageListener
{
public:
	friend void SendMessage(const std::string& message);
	IMessageListener();
	virtual ~IMessageListener();
protected:
	virtual void handle(cnst std::string& message)=0;
};

typedef std::vector<IMessageListener*> MessageListenerList;
typedef MessageListenerList::iterator MessageListenerListIt;

namespace {
MessageListenerList messageListenerList_;
}

IMessageListener::IMessageListener()
{
	messageListenerList_.push_back(this);
}

IMessageListener::~IMessageListener()
{
	messageListenerList_.erase(remove(messageListenerList_.begin(), messageListenerList_.end(), this), messageListenerList_.end());
}

こんなメッセージ送信クラスを書いた場合。
何らかのイベントをメッセージ送信によって各クラスに横断的に伝えようとする構造。
インターフェイスを継承することでこのリストに加わり、デストラクト時に消え去ることができる。
で、

void SendMessage(const std::string& message)
{
	for_each(
		messageListenerList_.begin(),
		messageListenerList_.end(),
		boost::bind(&IMessageListener::handle,_1,boost::ref(message))
	);
}

こんな送信をする場合。


簡単に各種クラスに送信してみます。
C++ code
- 106 lines - codepad

Bar::handle:test
Foo::handle:test
Hoge(0)::handletest
Hoge(1)::handletest
Hoge(2)::handletest
Hoge(3)::handletest
Hoge(4)::handletest
Hoge(5)::handletest

                                      • -

Bar::handle:test2
Foo::handle:test2
Hoge(0)::handletest2
Hoge(1)::handletest2
Hoge(2)::handletest2
Hoge(3)::handletest2
Hoge(4)::handletest2
Hoge(5)::handletest2

                                      • -

送信されています。
クラスBarはFooを持っていますが、
それを途中で消したくなったとします。
例えば「あるメッセージを受信したときに」。
では消してみましょう。

C++ code
- 108 lines - codepad

クラッシュした/(^o^)\
こいつはデバッグイテレータなのでちゃんと検出してくれましたが、
デバッグイテレータでない場合は不可解な挙動(例えば同じインスタンスのhandleが二回呼ばれるなど)が起こるケースがあります。
気をつけましょう!
(どうやってだ、ということに関して対策は続く! 予定)

あとfor_each便利なんでみんなもっと使おうぜ!