Entry

プログラミングメモ - C++ の強い型付けについて少し

2009年07月29日

割とよく使うテクニックだと思うんですけれど,ネットを調べた限りあまり扱ってないようなので,少しメモ。

C++ で任意の型を使ったメンバを持ったクラスを作るとき,テンプレート機能を使うことができます。例えば,幾何系プログラムで点クラスを作る場合,実数型の点クラスと離散型(整数型)の点クラスが欲しかったりしますよね(欲しいんです)。こゆとき,次のようなテンプレートを定義にすることで,メンバの値の型をコンパイル時に決めることができます。

template<typename T>
class Point {
public:
    // いろいろ
private:
    // T型のメンバ
    T x_;
    T y_;
};

これを使うときは,こんな風にします。

int
main(int argc, char* argv[]) {
    Point<int> discrete_point;  // 離散型の点オブジェクト
    Point<double> real_point;   // 実数型の点オブジェクト
    return 0;
}

ただこれ,よく使うクラスだったら,テンプレートのパラメータでメンバの型を指定するのは,ちと億劫ですよね。できればこんな風にしたい。

int
main(int argc, char* argv[]) {
    Point discrete_point;  // 離散型の点オブジェクト
    RealPoint real_point;  // 実数型の点オブジェクト
    return 0;
}

こゆ風にするのにまず思いつく方法としては,テンプレートの宣言を typedef してしまえば使えます。例えば,こんな感じで(テンプレートの点クラスを PointBase とします)。

typedef PointBase<int> Point;         // 離散型の点クラス
typedef PointBase<double> RealPoint;  // 実数型の点クラス

std::string クラスなんかは,std::basic_string<char> を typedef してるので,同じ方法です。でも,これはちと困る。どゆ時に困るのかと言うと,クラスを前方宣言するときです。ソース間のコンパイル依存関係を低減するために,前方宣言は不可欠なわけですけれど,typedef してしまうとテンプレートクラスまでさかのぼって宣言しなくちゃいけません。例えば,こんな感じ。

typedef PointBase<int> Point;

試してみた方は分かると思うんですけれど,std::string を前方宣言するのはとても大変だったりします,こゆのはなるべく避けたいところ。また,typedef は型の別名を宣言するだけで(弱い型付け),その型を定義するわけじゃありません。できればちゃんとしたクラスとして定義したいところです。

とゆことで,テンプレートクラスを,別名で強く型付けする方法を考えてみます。ま,単純に,テンプレートクラスを継承するだけなんですが。

// base template class
template<typename T>
class PointBase {
protected:
    PointBase() : x_(static_cast<T>(0)), y_(static_cast<T>(0)) {
    }
    PointBase(T x, T y) : x_(x), y_(y) {
    }
    PointBase(const PointBase& other) : x_(other.x_), y_(other.y_) {
    }
public:
    virtual ~PointBase() {
    }
public:
    typedef T PointType;
public:
    PointBase& operator=(const PointBase<T>& rhs) {
        if (this != &rhs) {
            x_ = rhs.x_;
            y_ = rhs.y_;
        }
        return *this;
    }
public:
    T getX() const {
        return x_;
    }
    T getY() const {
        return y_;
    }
protected:
    T x_;
    T y_;
};

// discrete point class
class Point : public PointBase<Int32> {
public:
    Point();
    Point(PointType x, PointType y);
    Point(const Point& other);
public:
    Point operator=(const Point& rhs);
};

// real point class
class RealPoint : public PointBase<double> {
public:
    RealPoint();
    RealPoint(PointType x, PointType y);
    RealPoint(const RealPoint& other);
public:
    RealPoint operator=(const RealPoint& rhs);
};

これなら前方宣言も class Point; とかするだけでオッケーです。

また,どんな型で特殊化したかを宣言内だけで記述すればいいように,テンプレートクラス側で,特殊化した元の型名を PointType として typedef しています。こうしておけば,各変数の引数でメンバの型をいちいち変更する必要がありません。よく見てもらうと分かるんですけれど,Point クラスの宣言と,RealPoint クラスの宣言は,基底クラスとクラス名を除いてすべて同じ宣言になっています。コピーコンストラクタの引数型を double で指定する必要はありません。型を変えたくなったら,継承元のテンプレート引数だけを変えればいい。弱い型付けは,こういうときに生きるんですね(多分)。

もちろん,テンプレートから継承しているのは,処理を共通化するためなので,共通する処理はテンプレート側に書いておきます。例えば,点と点の距離を求めるメソッドなんかはこっちに書いておく。一方,typedef を使わずに継承で型付けすると,多少ディスパッチに時間がかかるかもしれません。けど,拡張性と取り扱いやすさから言うと,こゆ方法もありなんじゃないか,と。

最後に,Point クラスと RealPoint クラスの実装ファイルも紹介しておきます。

Point::Point() {
}

Point::Point(PointType x, PointType y)
    : PointBase<PointType>(x, y) {
}

Point::Point(const Point& other)
    : PointBase<PointType>(other.x_, other.y_) {
}

Point
Point::operator=(const Point& rhs) {
    PointBase<PointType>::operator=(rhs);
    return *this;
}


////////////////////////////////////////////////////////////////////////////////

RealPoint::RealPoint() {
}

RealPoint::RealPoint(PointType x, PointType y)
    : PointBase<PointType>(x, y) {
}

RealPoint::RealPoint(const RealPoint& other)
    : PointBase<PointType>(other.x_, other.y_) {
}

RealPoint
RealPoint::operator=(const RealPoint& rhs) {
    PointBase<PointType>::operator=(rhs);
    return *this;
}

基底クラスのメンバを呼ぶときは,PointBase<PointType>::* のようにパラメタを指定して呼ぶんですけれど,PointType に typedef しているので,機械的にコピペと置換を行えば,新しいクラスを作ることができちゃいます。微妙に便利。

ただ,これは,パラメタの型が外部から隠されちゃうので,使う側からすると少し不便かも。一応,Point::PointType とかで参照することはできるので,static_cast<Point::PointType>(*) とかしてもらえばいいんだけども,外から見ると結局どゆ値を与えればいいか分かりません。少なくともインターフェイス周りについては,PointType を使って公開しない方がいいのかも。

いろいろと工夫するところはありそうですね。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN