エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

すなおにデシリアライザを書くのが精神衛生上よい気がする

昨日書いた仮想コンストラクタのコード、何度読み返してみても気持ちが悪い。

ためしに、デシリアライザで書き直してみた。
やっぱりこっちの方が素直で間違いもない気がする。
少なくともわたしには、トリッキーなコードを書くだけのメリットが感じられず。
…読み進めるとなにかあるのかな…?


以下コード。

#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <stdexcept>

class Base
{
public:
    virtual ~Base() {}
    virtual void write(std::ostream& out) const = 0;

};

class Point : public Base
{
public:
    static const char name[];

    Point(std::istream& in)
    {
        in >> x_ >> y_;
    }

    void write(std::ostream& out) const
    {
        out << name << " " << x_ << " " << y_;
    }

private:
    int x_;
    int y_;
};

class Rectangle : public Base
{
public:
    static const char name[];

    Rectangle(std::istream& in)
    {
        in >> x_ >> y_ >> width_ >> height_;
    }

    void write(std::ostream& out) const
    {
        out << name << " " << x_ << " " << y_ << " " << width_ << " " << height_;
    }

private:
    int x_;
    int y_;
    int width_;
    int height_;
};

class Circle : public Base
{
public:
    static const char name[];

    Circle(std::istream& in)
    {
        in >> x_ >> y_ >> r_;
    }

    void write(std::ostream& out) const
    {
        out << name << " " << x_ << " " << y_ << " " << r_;
    }

private:
    int x_;
    int y_;
    int r_;
};

std::ostream& operator << (std::ostream& out, const Base& base)
{
    base.write(out);
    return out;
}

const char Point::name[]     = "point";
const char Rectangle::name[] = "rectangle";
const char Circle::name[]    = "circle";

// ストリームからオブジェクトを生成するクラステンプレート
template <class BASE>
class Deserializer
{
public:
    template <class DERIVED>
    void registerClass()
    {
        creators_[DERIVED::name] = create<DERIVED>;
    }

    BASE* load(std::istream& in) const
    {
        std::string type;
        in >> type;
        typename Creators::const_iterator i = creators_.find(type);
        if(i == creators_.end())
        {
            throw std::runtime_error("unknown type");
        }
        return i->second(in);
    }

private:
    typedef std::map<std::string, Base* (*)(std::istream&)> Creators;

    template <class DERIVED>
    static BASE* create(std::istream& in)
    {
        return new DERIVED(in);
    }

    Creators creators_;
};

// テスト
void test(const Deserializer<Base>& deserializer)
{
    std::istringstream pointSource("point 10 20");
    Base* p = deserializer.load(pointSource);
    std::cout << *p << std::endl;
    delete p;

    std::istringstream rectangleSource("rectangle 10 20 30 40");
    p = deserializer.load(rectangleSource);
    std::cout << *p << std::endl;
    delete p;

    std::istringstream circleSource("circle 100 200 300");
    p = deserializer.load(circleSource);
    std::cout << *p << std::endl;
    delete p;
}

int main(int argc, char* argv[])
{
    Deserializer<Base> deserializer;

    deserializer.registerClass<Point>();
    deserializer.registerClass<Rectangle>();
    deserializer.registerClass<Circle>();

    test(deserializer);

    return 0;
}


実行結果。

point 10 20
rectangle 10 20 30 40
circle 100 200 300

追記。こうした方が少し便利かもしれない。

template <class BASE>
class Deserializer
{
// ...
    BASE* load(std::istream& in) const
    {
        if(in.bad())
        {
            return 0;
        }
        std::string type;
        in >> type;
        typename Creators::const_iterator i = creators_.find(type);
        if(i == creators_.end())
        {
            throw std::runtime_error("unknown type");
        }
        return i->second(in);
    }
// ...
};

void test(const Deserializer<Base>& deserializer)
{
    Base* p;
    std::istringstream source("point 10 20 rectangle 10 20 30 40 circle 100 200 300");

    while(source.good() && (p = deserializer.load(source)))
    {
        std::cout << *p << std::endl;
        delete p;
    }
}