DATE : 2006/10/01 (Sun)
前回の記事の最後に、点オブジェクトを表すインタフェースとしてのクラスを作成しました。
class Point { public: virtual ~Point() { } virtual Point* clone() const = 0; virtual int getX() const = 0; virtual int getY() const = 0; virtual string toString() const = 0; };
このインタフェースを実装して、点そのものを表すクラスを考えます。
class PlainPoint : public Point { public : PlainPoint(); PlainPoint(const int& x, const int& y); Point* clone() const; int getX() const; int getY() const; string toString() const; private : int x; int y; }; PlainPoint::PlainPoint() : x(0), y(0) { } PlainPoint::PlainPoint(const int& x, const int& y) : x(x), y(y) { } Point* PlainPoint::clone() const { return new PlainPoint(this->x, this->y); } int PlainPoint::getX() const { return this->x; } int PlainPoint::getY() const { return this->y; } string PlainPoint::toString() const { ostringstream stream; stream << (this->x) << ", " << (this->y); return stream.str(); }
(;^ω^)コンストラクタの引数を参照にしたり、文字列を生成したりする部分以外はほとんど前の Point クラスと同じです。
例えば、次のように PlainPoint オブジェクトの文字列表現を出力してみます。
PlainPoint point(2, 3); cout << point.toString() << endl;
すると、次のような結果になります。
2, 3
ここで、次の部分に注目してください。
class PlainPoint : public Point { public : PlainPoint(); PlainPoint(const int& x, const int& y); Point* clone() const; int getX() const; int getY() const; string toString() const;
クラスの宣言に「: public 基底クラス」とすることで、基底クラスに指定したクラスを継承することができます。ちなみに、「public」とは、基底クラスの public メンバを派生クラスでもそのまま public にするという意味です。ここが「protected」だと基底クラスの public メンバは protected になりますし、「private」の場合は基底クラスのメンバ全てが private になります(public を省略した場合は、private になります)
続いて、PlainPoint クラスのコンストラクタ、コピーコンストラクタと続いて、この PlainPoint クラスで実装するメンバを並べています。virtual 修飾子は付いていませんが、基底クラスで(純粋)仮想関数であると宣言されているので、virtual 修飾子は必要ありません(ただし、省略可能であってもあえて virtual 修飾子を書く流派もあるようです)。
後は、普通にクラスを定義する場合と同じです。
続いて、修飾を施した点を表すクラスを考えてみます。
しかし、一口に「修飾」と言っても、修飾の種類は数多くあります。今回は文字列表現をいじるだけですので、カッコや名前を付けたりといった程度しかありませんが、実際に描画する際にはさらに多くの修飾が考えられます。例えば、周辺に及ぼす効果や色などが挙げられます。
ということで、まず修飾を施した点に共通した機能を抽出して抽象クラスを作ってみたいと思います。修飾を施した点というのは、修飾対象の点オブジェクトを保持していると考えられます。そこで、点オブジェクトを保持する DecoratedPoint クラスを考えます。
class DecoratedPoint : public Point { public: DecoratedPoint(Point *point); DecoratedPoint(const DecoratedPoint& source); virtual ~DecoratedPoint(); DecoratedPoint& operator=(const DecoratedPoint& source); Point* clone() const; int getX() const; int getY() const; string toString() const; private: Point* point; }; DecoratedPoint::DecoratedPoint(Point* point) : point(point) { } DecoratedPoint::DecoratedPoint(const DecoratedPoint& source) : point(source.point->clone()) { } DecoratedPoint& DecoratedPoint::operator=(const DecoratedPoint& source) { if (&source == this) { return *this; } delete this->point; this->point = source.point->clone(); return *this; } DecoratedPoint::~DecoratedPoint() { delete this->point; } Point* DecoratedPoint::clone() const { return this->point->clone(); } int DecoratedPoint::getX() const { return this->point->getX(); } int DecoratedPoint::getY() const { return this->point->getY(); } string DecoratedPoint::toString() const { return this->point->toString(); }
(;^ω^)やっていることは単純で、コンストラクタで受け取った Point オブジェクトに Point クラスとしての処理を横流しにしているだけです。
続いて、点に名前を付けるクラスを考えてみます。
(;^ω^)ただし、簡単にするため名前を変更する機能はありません。
class NamedPoint : public DecoratedPoint { public: NamedPoint(const string& name, Point* point); NamedPoint(const NamedPoint& source); NamedPoint& operator=(const NamedPoint& source); Point* clone() const; string toString() const; private: string name; }; NamedPoint::NamedPoint(const string& name, Point* point) : DecoratedPoint(point), name(name) { } NamedPoint::NamedPoint(const NamedPoint& source) : DecoratedPoint(source), name(source.name) { } NamedPoint& NamedPoint::operator=(const NamedPoint& source) { if (&source == this) { return *this; } DecoratedPoint::operator=(source); this->name = source.name; return *this; } Point* NamedPoint::clone() const { return new NamedPoint(this->name, DecoratedPoint::clone()); } string NamedPoint::toString() const { return this->name + DecoratedPoint::toString(); }
ここで、次の部分に注目してください。
NamedPoint::NamedPoint(const NamedPoint& source) : DecoratedPoint(source), name(source.name) { }
基底クラスのコンストラクタを初期化宣言で呼び出しています。このようにすると、基底クラスのコンストラクタを呼び出すことができます。
また、次の部分にも注目してください。
Point* NamedPoint::clone() const { return new NamedPoint(this->name, DecoratedPoint::clone()); }
「DecoratedPoint::clone()」とすることで、基底クラス DecoratedPoint の clone 関数を呼び出しています。このように、基底クラス名を指定すると、派生クラスからそのクラスの関数を呼び出すことができます。
(;^ω^)Java の super と少し似ていますね。
(;^ω^)ちなみに、上のように clone 関数を使わずに、保持している Point オブジェクトを返す関数を DecoratedPoint クラスに定義するという方法もあります。しかし、そのようにすると、修飾を行うクラスが修飾対象のオブジェクトに過剰に干渉できてしまうので、あえて保持している Point オブジェクトにアクセスできないようにしました。
最後に、カッコ付けを行うクラスを考えてみます。
class BracketedPoint : public DecoratedPoint { public: BracketedPoint(const string& left, const string& right, Point* point); BracketedPoint(const BracketedPoint& source); BracketedPoint& operator=(const BracketedPoint& source); Point* clone() const; string toString() const; private: string left; string right; }; BracketedPoint::BracketedPoint(const string& left, const string& right, Point* point) : DecoratedPoint(point), left(left), right(right) { } BracketedPoint::BracketedPoint(const BracketedPoint& source) : DecoratedPoint(source), left(source.left), right(source.right) { } BracketedPoint& BracketedPoint::operator=(const BracketedPoint& source) { if (&source == this) { return *this; } DecoratedPoint::operator=(source); this->left = source.left; this->right = source.right; return *this; } Point* BracketedPoint::clone() const { return new BracketedPoint( this->left, this->right, DecoratedPoint::clone()); } string BracketedPoint::toString() const { return this->left + DecoratedPoint::toString() + this->right; }
(;^ω^)このクラスで特に取り上げることはありません。
以上のクラスを使って、様々に修飾を施した、点の文字列表現を生成してみます。
次の例は、「name0, 0」となります。
NamedPoint point("name", new PlainPoint); cout << point.toString() << endl;
次の例は、「name(2, 3)」となります。
BracketedPoint point("(", ")", new PlainPoint(2, 3)); NamedPoint namedPoint("name", point.clone()); cout << namedPoint.toString() << endl;
次の例は、「[name(5, 6)]」となります。
BracketedPoint point("(", ")", new PlainPoint(5, 6)); NamedPoint namedPoint("name", point.clone()); BracketedPoint bracketedPoint("[", "]", namedPoint.clone()); cout << bracketedPoint.toString() << endl;
ちなみに、これらは全て Point クラスとして扱えるので、Line クラスに渡して使うことも可能です。例えば、以下の例では「[name(5, 6)] - 2, 3」となります。
BracketedPoint point1("(", ")", new PlainPoint(5, 6)); NamedPoint namedPoint("name", point1.clone()); BracketedPoint bracketedPoint("[", "]", namedPoint.clone()); PlainPoint point2(2, 3); Line line(bracketedPoint.clone(), point2.clone()); cout << line.toString() << endl;
ちなみに、Line クラスも今回と同様の方法で拡張していくことができます。
DATE : 2006/09/30 (Sat)
Java では、クラスの持つ仕様を定義したインタフェースと呼ばれる型があります。
C++ でインタフェースを実現するには、純粋仮想関数を使用します。純粋仮想関数とは、実装を派生クラスに任せるための関数です。
Java で言うとこれは abstract メソッドに相当します。例えば、次のような abstract クラスがあったとします。
public abstract class AbstractCommand { private final String name; public AbstractCommand(String name) { if (name == null) { throw new IllegalArgumentException(); } this.name = name; } public abstract void execute(); public String getName() { return this.name; } }
C++ では次のようになります。(インクルード文などは省略しています)
class AbstractCommand { private: string name; public: AbstractCommand(string& name); virtual ~AbstractCommand() { } virtual void execute() = 0; string getName() const; }; AbstractCommand::AbstractCommand(string& name) : name(name) { } string AbstractCommand::getName() const { return this->name; }
ここで、次の部分に注目してください。
virtual void execute() = 0;
virtual と修飾子を付けたメンバ関数の中身を「= 0」とすると、「純粋仮想関数」になります。ちなみに、「= 0」を付けずに処理を定義すると、「仮想関数」になります。例えば、あらかじめ標準的な処理を定めておきたい場合には、次のように仮想関数にします。
class AbstractCommand { private: string name; public: AbstractCommand(string& name); virtual ~AbstractCommand() { } virtual void execute(); string getName() const; }; AbstractCommand::AbstractCommand(string& name) : name(name) { } void AbstractCommand::execute() { cout << "Command not found." << endl; } string AbstractCommand::getName() const { return this->name; }
「仮想関数」にすると、その関数を派生クラスでオーバーライドすることができます。言い換えると、「仮想関数」でなければオーバーライドできません。
(;^ω^)Java の場合、メソッドに final 修飾子を付けない限りはサブクラスで自由にオーバーライドできますが、C++ はその逆ですね。
ところで、virtual の付いた次のデストラクタは何でしょうか。
virtual ~AbstractCommand() { }
これは「仮想デストラクタ」と呼ばれています。virtual を付けることで、派生クラスにもデストラクタがあることを示しています。仮想デストラクタがない場合は、呼び出したときの型から上位のクラスにあるデストラクタのみが呼び出されます。例えば、上のコードに仮想デストラクタがなかった場合、次のコードでは AbstractCommand のデストラクタしか呼ばれません。
// ListCommand は AbstractCommand を継承 AbstractCommand* command = new ListCommand(); delete command;
そのため、派生クラスがある場合は必ずと言っても良いほど「仮想デストラクタ」を用意しなければなりません。
ちなみに、純粋仮想関数が1つでもクラスにあると、そのクラスのインスタンスは生成できません。
以上から、インタフェースを実現するには、メンバ変数をなくして、メンバ関数を全て純粋仮想関数にすれば良いことが分かります。
例えば、これまでに出てきた Point クラスをインタフェースにすると、次のようになります。(インクルード文などは省略しています)
class Point { public: virtual ~Point() { } virtual Point* clone() const = 0; virtual int getX() const = 0; virtual int getY() const = 0; virtual string toString() const = 0; };
次回は、このインタフェースを利用して装飾可能な点オブジェクトを作ってみたいと思います。例えば、前回までの Point オブジェクトの場合、文字列表現は必ず「(0, 0)」という形になっていました。そこで次回は、「0, 0」や「name(0, 0)」、「(name[0, 0])」と生成する Point オブジェクトを考えてみます。
(;^ω^)実際に点を描く処理であれば点の形を変えたりと非常に華やかなのですが、文字列表現をいじるだけにとどめたいと思います。
DATE : 2006/09/29 (Fri)
C++ で文字列を扱うには、標準C++ライブラリの string クラスを使用します。C と同じように char 型の配列としても扱えますが、ここでは string クラスを使った方法を取り上げます。
文字列の静的な宣言
string クラスを使用するには、まず string ヘッダを取り込む必要があります。
#include <string>
string オブジェクトを文字列リテラルで宣言するには、次のようにします。
std::string str = "string";
なお、「string」の前にある「std」は「名前空間」です。string クラスは std 名前空間に属しているので、上のように書きます。Java で言えば、次のようなコードに相当します。
java.lang.String str = "string";
Java の場合、java.lang パッケージは自動的にインポートされるので、上のように java.lang と書く必要はありません。
ただし、C++ の場合でも、あらかじめ使用する名前空間を宣言しておくことで、クラスの名前空間を省略できます。
例えば、ファイル中で std 名前空間を使用するときは次のように書きます。
using namespace std;
これは、Java の場合は次のようなコードに相当します。
import java.lang.*;
ちなみに、名前空間の表記を特定のクラスのみ省略する場合は、次のように書きます。
using std::string;
これは、Java の場合は次のようなコードに相当します。
import java.lang.String;
文字列の動的な生成
Java では、+演算子を使うことで文字列を結合できます。
String str = "str" + "ing";
string オブジェクトも、+演算子で文字列を結合できます。ただし、文字列リテラル同士はアドレス同士の加算となるので、結合できません。そのため、string オブジェクトを作成した上での加算となります。
string s1 = "str"; string s2 = "ing"; string str = s1 + s2;
もしくは、次のようにも書けます。
string str = string("str") + string("ing");
また、文字列ストリームを使う方法もあります。
文字列ストリームを使うには、sstream ヘッダを取り込みます。
#include <sstream>
文字列ストリームを使って文字列を生成するコードを以下に示します。
std::ostringstream stream; stream << "str" << "ing"; std::string str = stream.str();
サンプルコード
ここで、 Point クラス、Line クラス に、オブジェクトの情報を文字列として返す toString メソッドを作ります。
(;^ω^)本当は、実際に点や線を描くメソッドを設けた方がそれらしいのですが、簡単のために文字列にしました。
追加したコードは次の通りです。
#include <string> #include <sstream> using std::string; using std::ostringstream; class Point { private : int x; int y; public : Point(); Point(int x, int y); Point* clone() const; int getX() const; int getY() const; string toString() const; }; Point::Point() : x(0), y(0) { } Point::Point(int x, int y) : x(x), y(y) { } Point* Point::clone() const { return new Point(this->x, this->y); } int Point::getX() const { return this->x; } int Point::getY() const { return this->y; } string Point::toString() const { ostringstream stream; stream << "(" << (this->x) << ", " << (this->y) << ")"; return stream.str(); } class Line { private : const Point *start; const Point *end; public : Line(const Point* start, const Point* end); Line(const Line& line); ~Line(); Line& operator=(const Line& line); const Point& getStart() const; const Point& getEnd() const; string toString() const; }; Line::Line(const Point* start, const Point* end) : start(start), end(end) { } Line::Line(const Line& line) : start(line.getStart().clone()), end(line.getEnd().clone()) { } Line::~Line() { delete this->start; delete this->end; } Line& Line::operator=(const Line& line) { if (this == &line) { return *this; } delete this->start; delete this->end; this->start = line.getStart().clone(); this->end = line.getEnd().clone(); return *this; } const Point& Line::getStart() const { return *(this->start); } const Point& Line::getEnd() const { return *(this->end); } string Line::toString() const { ostringstream stream; stream << (this->start->toString()) << " - " << (this->end->toString()); return stream.str(); }
変更した部分は、次の通りです。
#include <string> #include <sstream> using std::string; using std::ostringstream;
class Point { private : int x; int y; public : Point(); Point(int x, int y); Point* clone() const; int getX() const; int getY() const; string toString() const; };
string Point::toString() const { ostringstream stream; stream << "(" << (this->x) << ", " << (this->y) << ")"; return stream.str(); }
class Line { private : const Point *start; const Point *end; public : Line(const Point* start, const Point* end); Line(const Line& line); ~Line(); Line& operator=(const Line& line); const Point& getStart() const; const Point& getEnd() const; string toString() const; };
string Line::toString() const { ostringstream stream; stream << (this->start->toString()) << " - " << (this->end->toString()); return stream.str(); }
このようにしておくと、次のように Line オブジェクトの情報を toString メソッドを呼び出すだけで取得することができます。
Line line(new Point(0, 0), new Point(10, 10)); std::cout << line.toString() << std::endl;
上のコードを実行すると、次のように表示されます。
(0, 0) - (10, 10)
DATE : 2006/09/28 (Thu)
「ポケットモンスター ダイヤモンド」(ポケモン)を購入しました。
(;´Д`)本当はしばらくは様子見のつもりでしたが、衝動買いしてしまいました
(;^ω^)友人と昔のポケモンの話で盛り上がったために、購入欲のメータが振り切れてしまいました
夜になって、近くのトイザらスへ買いに走ったのですが、ほとんど売り切れ状態でした。入口に展示してあったディスプレイの中は空っぽ、DS コーナーに並べられていたであろうスペースも空っぽと言う状態でした。
唯一残っていたのは、DS 本体が展示されているショーケースの脇にあったチケット数枚でした。
(;^ω^)同じポケモンを買い求めに来た他の親子連れが、「これじゃない?」と言っていたのを背後で聞かなかったら、見逃していたかもしれません。
(;´Д`)もっとも、赤・緑時代はものすごくハマッたものですが、金・銀、ルビー・サファイアと来るに従ってクリアする前に放り投げてしまっているので、正直今作もどこまでモチベーションを保てるか心配なところではあります
DATE : 2006/09/28 (Thu)
これから継承の方面へと日記を進めていく予定ですが、これまでのサンプルコードに問題が見つかったのでここで修正しておきます。
これまでのサンプルコードで問題点が見つかったのは、次の部分です。
Line::Line(const Line& line) : start(new Point(line.getStart())), end(new Point(line.getEnd())) { }
Line& Line::operator=(const Line& line) { if (this == &line) { return *this; } delete this->start; delete this->end; this->start = new Point(line.getStart()); this->end = new Point(line.getEnd()); return *this; }
これから先の記事では Point クラスのサブクラスが出てくる予定です。しかし、上のソースコードを見ると、Line オブジェクトのメンバとして Point オブジェクトを決め打ちで生成しています。このままでは、Point クラスのサブクラスが出てきても Line オブジェクトのコピーや代入を行うと、中身が Point クラスそのものに変化してしまいます。
そこで次のように、Point クラスにコピーコンストラクタの代わりとして clone メソッドを用意して、コンストラクタに依存しない方法で Point オブジェクトのコピーを行うようにします。
class Point { private : int x; int y; public : Point(); Point(int x, int y); Point* clone() const; int getX() const; int getY() const; }; Point::Point() : x(0), y(0) { } Point::Point(int x, int y) : x(x), y(y) { } Point* Point::clone() const { return new Point(this->x, this->y); } int Point::getX() const { return this->x; } int Point::getY() const { return this->y; } class Line { private : const Point *start; const Point *end; public : Line(const Point* start, const Point* end); Line(const Line& line); ~Line(); Line& operator=(const Line& line); const Point& getStart() const; const Point& getEnd() const; }; Line::Line(const Point* start, const Point* end) : start(start), end(end) { } Line::Line(const Line& line) : start(line.getStart().clone()), end(line.getEnd().clone()) { } Line::~Line() { delete this->start; delete this->end; } Line& Line::operator=(const Line& line) { if (this == &line) { return *this; } delete this->start; delete this->end; this->start = line.getStart().clone(); this->end = line.getEnd().clone(); return *this; } const Point& Line::getStart() const { return *(this->start); } const Point& Line::getEnd() const { return *(this->end); }
変更点は以下の通りです。
class Point { private : int x; int y; public : Point(); Point(int x, int y); Point* clone() const; int getX() const; int getY() const; };
Point* Point::clone() const { return new Point(this->x, this->y); }
Line::Line(const Line& line) : start(line.getStart().clone()), end(line.getEnd().clone()) { }
Line& Line::operator=(const Line& line) { if (this == &line) { return *this; } delete this->start; delete this->end; this->start = line.getStart().clone(); this->end = line.getEnd().clone(); return *this; }
( ´∀`)このようにしておけば、いつ Point クラスにサブクラスを作っても、Line クラスは変更しなくていいので楽ですね
(;^ω^)ちなみにもうひとつ、Line クラスのコンストラクタにヌルポインタを渡した時の動作にも問題点があります。ただこの部分は、例外処理に触れた後で解決を図ってみようと思います。