step-4: ちゃんと死ぬObserver
さてこのコード、三度のroom.increment()の後、dispがその役目を終えこの世から消えたはずなのに、その後のroom.decrement()に反応してしまいます。イベントの発行元であるRoomTemp、すなわちSubjectがshared_ptr<ObserverBody>を持っているため、ObserverBodyを捕まえたまま手放さないからです。Observerが死ぬときには同時にObserverBodyも死んでほしい、さらにSubjectはObserverBodyの生死を判断し、生きている時に限ってupdateしたい。こんな振る舞いを実現すべく、Subjectが内包するvector<shared_ptr<ObserverBody>>をvector<weak_ptr<ObserverBody>>に改めます。weak_ptr(弱参照ポインタ)はそれが指すオブジェクトの生死を判定することができます。
/* [step-4]
* std::weak_ptr(弱参照)によるObserverパターン実装
*/
#include <vector>
#include <memory>
#include <algorithm>
class ObserverBody {
friend class Subject;
public:
virtual ~ObserverBody() {}
protected:
virtual void update(Subject*) =0; // コレが呼び出されるハンドラ
};
class Observer {
friend class Subject;
typedef std::shared_ptr<ObserverBody> body_type;
private:
body_type body_;
protected:
void set_body(body_type body) { body_ = body; }
body_type body() const { return body_; }
public:
virtual ~Observer() {}
};
class Subject {
private:
std::vector<std::weak_ptr<ObserverBody>> obs_;
public:
// Observerを登録
void add_observer(Observer& o) {
if ( std::find_if(obs_.begin(), obs_.end(),
[&](std::weak_ptr<ObserverBody> w)
{ return w.lock() == o.body(); }) == obs_.end() ) {
obs_.push_back(o.body());
}
}
// Observerの登録抹消
void delete_observer(Observer& o) {
auto iter = std::find_if(obs_.begin(), obs_.end(),
[&](std::weak_ptr<ObserverBody> w)
{ return w.lock() == o.body(); });
if ( iter != obs_.end() ) {
obs_.erase(iter);
}
}
protected:
// 登録されたObserverのハンドラを呼び出す
void notify() {
// お掃除ののち、ハンドラを呼び出す
sweep();
std::for_each(obs_.begin(), obs_.end(),
[this](std::weak_ptr<ObserverBody> w)
{ w.lock()->update(this);});
}
public:
// 賞味期限の切れたポインタを取り除く
void sweep() {
obs_.erase(std::remove_if(obs_.begin(), obs_.end(),
[](std::weak_ptr<ObserverBody> w)
{ return w.expired(); }),
obs_.end());
}
};
/*
* おためし
*/
#include <iostream>
// 室温
class RoomTemp : public Subject {
private:
int value_; //
public:
RoomTemp(int initial) : value_(initial) {}
void increment() // 室温を1度上げる
{ ++value_; notify(); }
void decrement() // 室温を1度下げる
{ --value_; notify(); }
int get_temp() const // 現在の室温
{ return value_; }
};
// 温度表示パネル
class TempDispBody : public ObserverBody {
public:
~TempDispBody() { std::cout << "お疲れ様でしたー" << std::endl; }
protected:
virtual void update(Subject* from) {
int temp = static_cast<RoomTemp*>(from)->get_temp();
std::cout << "現在の温度: " << temp << "℃" << std::endl;
}
};
class TempDisp : public Observer {
public:
TempDisp() { set_body(std::make_shared<TempDispBody>()); }
};
int main() {
{
RoomTemp room(20);
{
TempDisp disp;
room.add_observer(disp);
room.increment(); room.increment(); room.increment();
// room.delete_observer(disp);
std::cout << "TempDisp御役御免" << std::endl;
}
room.decrement(); room.decrement(); room.decrement();
}
std::cout << "これにて一件落着" << std::endl;
}
とまぁそんなわけで、shared_ptrで2つのパターン、ObserverとBridgeを実装してみました。ガベージコレクタのないC++において、shared_ptrは強力な手駒となりますね。
