忍者ブログ
[5] [6] [7] [8] [9] [10]

DATE : 2024/04/20 (Sat)
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


DATE : 2006/09/24 (Sun)
Line line(new Point, new Point(1, 1));

Line line2 = line;

上のように変数 line2 を変数 line のオブジェクトで初期化したとき、line のオブジェクトが line2 に「コピー」されます。また、値渡しで関数にオブジェクトを渡す際や返す際にもオブジェクトは「コピー」されます。

Line line(new Point, new Point(1, 1));
Line line2(new Point(1, 1), new Point(2, 2));

line2 = line;

ちなみに、上のように宣言済みの変数 line2 に line のオブジェクトを代入する場合は、コピーではなく「代入」と呼ばれます。

オブジェクトのコピーや代入が行われると、コピー元のメンバ変数の値がコピー先のメンバ変数にコピーされます。

しかし、メンバ変数にポインタ変数がある場合には問題が発生します。例えば、次の Line クラスを考えてみます。

class Line {
private :
	const Point *start;
	const Point *end;

public :
	Line(const Point* start, const Point* end);
	~Line();
	const Point& getStart() const;
	const Point& getEnd() const;
};

Line::Line(const Point* start, const Point* end) :
	start(start), end(end) { }

Line::~Line() {
	delete this->start;
	delete this->end;
}

const Point& Line::getStart() const {
	return *(this->start);
}

const Point& Line::getEnd() const {
	return *(this->end);
}

ここで、次のようなプログラムを考えます。

void function(Line line) {
	// line に関する処理
}

int main() {
	Line line(new Point, new Point(1, 1));

	function(line);

	return 0;
}

関数 function は、Line オブジェクトを値渡しで行って処理する関数です。そこに、Line オブジェクトを渡すのが main 関数なのですが、このプログラムでは実行時にエラーが発生します。

というのも、関数 function に値渡しを行った際にはオブジェクトの「コピー」が行われるからです。コピーが行われると、変数 line のメンバ変数 line が引数 line にコピーされることになります。そのとき、Line オブジェクトの持っている Point オブジェクトへのポインタもコピーされることになります。

すると、変数 line と引数 line は別々の Line オブジェクトにもかかわらず、持っている Point オブジェクトへのポインタは同じ Point オブジェクトを指すことになります。

そして関数 function が終了すると、引数 line の Line オブジェクトは破棄されます。この時 Line クラスのデストラクタが呼ばれ、変数 line と引数 line が共有していた Point オブジェクトをも破棄してしまうことになります。

そこで、Line オブジェクトがコピーされる際には、Point オブジェクトも複製しなければなりません。この「コピー」される際に呼び出されるのが、「コピーコンストラクタ」です。

「コピーコンストラクタ」を追加した Line クラスを次に示します。

class Line {
private :
	const Point *start;
	const Point *end;

public :
	Line(const Point* start, const Point* end);
	Line(const Line& 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) {
	const Point& start = line.getStart();
	const Point& end = line.getEnd();

	this->start = new Point(start.getX(), start.getY());
	this->end = new Point(end.getX(), end.getY());
}

Line::~Line() {
	delete this->start;
	delete this->end;
}

const Point& Line::getStart() const {
	return *(this->start);
}

const Point& Line::getEnd() const {
	return *(this->end);
}

ここで、次の部分に注目してください。

Line(const Line& line);
Line::Line(const Line& line) {
	const Point& start = line.getStart();
	const Point& end = line.getEnd();

	this->start = new Point(start.getX(), start.getY());
	this->end = new Point(end.getX(), end.getY());
}

自分と同じクラスのオブジェクトの const 付き参照を受け取るコンストラクタが「コピーコンストラクタ」です。このコンストラクタが、「コピー」の際に呼び出されます。

(;^ω^)ちなみに、上のコードの場合は、Point クラスの方にコピーコンストラクタを用意して、それを Line クラスから呼び出した方が綺麗かもしれません。例えば次のように、です。

class Point {
private :
	int x;
	int y;

public :
	Point();
	Point(int x, int y);
	Point(const Point& point);
	int getX() const;
	int getY() const;
};

Point::Point() : x(0), y(0) { }

Point::Point(int x, int y) : x(x), y(y) { }

Point::Point(const Point& point) : x(point.getX()), y(point.getY()) { }

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();
	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(new Point(line.getStart())),
		end(new Point(line.getEnd())) {
}

Line::~Line() {
	delete this->start;
	delete this->end;
}

const Point& Line::getStart() const {
	return *(this->start);
}

const Point& Line::getEnd() const {
	return *(this->end);
}

(;^ω^)追加・変更した部分は次の通りです。

(Point クラス)

Point(const Point& point);
Point::Point(const Point& point) : x(point.getX()), y(point.getY()) { }

(Line クラス)

Line::Line(const Line& line) :
		start(new Point(line.getStart())),
		end(new Point(line.getEnd())) {
}

オブジェクトの代入については、次の記事に書こうと思います。

PR

DATE : 2006/09/23 (Sat)

Java では、どこからも参照できなくなったオブジェクトはガーベジコレクタが検出してメモリを解放してくれます。C++ でも、以下のように宣言したオブジェクトは、スコープの終了とともに自動的に解放されます。

void func() {
	Point point;

	// 処理
}

しかし、動的に生成したオブジェクトは自分で破棄しなければなりません。

(;^ω^)「スマートポインタ」というものを使うと自動的に解放してくれるそうですが、今回は触れません。

動的に生成したオブジェクトを破棄するには、オブジェクトへのポインタに対して delete 演算子を使います。

Point* point = new Point;

delete point;

C でのメモリの解放と同様に、解放済みのポインタに対して delete を行うと実行時にエラーが発生します。

ところで、動的に確保したオブジェクトがメンバ変数にある場合はどのように解放すれば良いのでしょうか。

ここで、 前回の記事の Point クラスを使って2次元座標上の線分を表す Line クラスを考えてみます。

Java で表現したコードは次の通りです。

public class Line {
	private final Point start;
	private final Point end;

	public Line(Point start, Point end) {
		this.start = start;
		this.end = end;
	}

	public Point getStart() {
		return this.start;
	}

	public Point getEnd() {
		return this.end;
	}
}

(;^ω^)本当はコンストラクタの引数が null でないかどうかをチェックしないといけないのですが、ここでは省略しています。

C++ で表現すると次のようになります。

class Line {
private :
	const Point *start;
	const Point *end;

public :
	Line(const Point* start, const Point* end);
	~Line();
	const Point& getStart() const;
	const Point& getEnd() const;
};

Line::Line(const Point* start, const Point* end) :
	start(start), end(end) { }

Line::~Line() {
	delete this->start;
	delete this->end;
}

const Point& Line::getStart() const {
	return *(this->start);
}

const Point& Line::getEnd() const {
	return *(this->end);
}

ここで、次の部分に注目してください。

~Line();
Line::~Line() {
	delete this->start;
	delete this->end;
}

「~クラス名()」の形のメンバを「デストラクタ」と言い、そのクラスのオブジェクトが解放されるタイミングで呼び出されます。「解放されるタイミング」とは、前述にあるような、「スコープを外れた時」や「delete を行った時」のことです。


DATE : 2006/09/22 (Fri)

2次元座標の点を表す Point クラスを考えてみます。

Java の場合は以下のようなクラスになります。

public class Point {
	private int x;
	private int y;

	public Point() { }

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return this.x;
	}

	public int getY() {
		return this.y;
	}
}

引数なしのコンストラクタの場合は原点 (0, 0) を表し、引数を指定したコンストラクタでは引数に渡された座標を設定します。

このコードを C++ のクラスで表現すると次のようになります。

class Point {
private :
	int x;
	int y;

public :
	Point();
	Point(int x, int y);
	int getX() const;
	int getY() const;
};

Point::Point() : x(0), y(0) { }

Point::Point(int x, int y) : x(x), y(y) { }

int Point::getX() const {
	return this->x;
}

int Point::getY() const {
	return this->y;
}

Java の場合、上の Point オブジェクトを生成するには次のようにします。

Point origin = new Point();
Point point = new Point(1, 2);

C++ の場合は、基本的には次のようになります。

Point origin;
Point point(1, 2);

コンストラクタに渡す引数を指定しながら変数宣言をするイメージです。

ちなみに、Java の場合、origin, point の両変数に入るのは生成されたオブジェクトの参照ですが、上の C++ の場合はオブジェクトそのものになります。

なお、引数なしのコンストラクタを使用する場合、Java 風の書き方だと「Point origin();」と書きがちですが、この場合はコンパイルエラーとなります。

上の方法で生成した場合、次のようにしてクラスの public メンバにアクセスできます。

int originX = origin.getX();
int originY = origin.getY();

ただし、上の方法の場合、必要な時に必要な分だけ(動的に)Point オブジェクトを生成することができません。動的に Point オブジェクトを生成するには、次のようにします。

Point* origin = new Point;
Point* point = new Point(1, 2);

(,,゚Д゚)ポインタが出てきました。

new 演算子を使うと、生成されたオブジェクトのアドレスが返ってきます。

動的に生成したオブジェクトを使う場合は、C 同様アロー演算子(->)を使います。

int originX = origin->getX();
int originY = origin->getY();

ところで、動的に確保したメモリの領域は解放しなければならない C のように、C++ でも動的に生成したオブジェクトは破棄しなければなりません。そこで、次の記事はオブジェクトの破棄について書こうと思います。


DATE : 2006/09/21 (Thu)

(;^ω^)ある程度 C++ の勉強が進みましたので、さっそく勉強日記を付けていこうと思います。間違いがあった場合は、ちょこちょこと修正していきます。

C++ を勉強し始めたわけですが、Java を使っている身としてはやはり、クラスをどのように書くのか気になります。

とりあえず、次のように書かれた Java のクラスを C++ のクラスに書き直してみます。

(Java のクラス)

class Person {
	private String name;
	private int age;

	public Persion(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return this.name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getAge() {
		return this.age;
	}
}

name プロパティが読み出し専用で age プロパティが読み書き可能な Person クラスです。コンストラクタで両者を設定します。

このクラスを C++ で表現すると、次のようになります。

(C++ のクラス)

#include <string>

using std::string;

class Person {
private :
	string name;
	int age;
public :
	Person(const string& name, int age);
	string getName() const;
	void setAge(int age);
	int getAge() const;
};

Person::Person(const string& name, int age) : name(name), age(age) { }

string Person::getName() const {
	return this->name;
}

void Person::setAge(int age) {
	this->age = age;
}

int Person::getAge() const {
	return this->age;
}

string を使っているので少々余計な文がありますが、クラスの定義だけを取り出すと次のようになります。

class Person {
private :
	string name;
	int age;
public :
	Person(const string& name, int age);
	string getName() const;
	void setAge(int age);
	int getAge() const;
};

( ^ω^)セミコロンがクラス定義の最後に必要なところが、ちょっと C っぽいですね。

同じアクセス修飾子のフィールドやメソッドはまとめて書くところなどを除けば、それほど難しくはありません。

ちなみに、Java では「フィールド」「メソッド」と呼んでいる言葉は、C++ ではそれぞれ「メンバ変数」「メンバ関数」と呼ばれているようです。

( ^ω^)ここらへん、C の構造体の「メンバ」から派生したような言葉のような感じがしますね

なおに、メンバ関数の後ろについている「const」は、そのメンバ関数がメンバ変数を書き換えないことを示しています。このキーワードは、メンバ変数を書き換えないのなら書くように推奨されているようです。

ところで、C++ のクラスにはアクセス修飾子はありません。クラスの書かれたファイル外にクラスを公開する場合は、ヘッダファイルにクラス定義を記述します。

( ^ω^)Java と違って、C++ のクラスはファイルスコープが最大のスコープのようです。

クラスの実装部分は次の通りです。

Person::Person(const string& name, int age) : name(name), age(age) { }

string Person::getName() const {
	return this->name;
}

void Person::setAge(int age) {
	this->age = age;
}

int Person::getAge() const {
	return this->age;
}

クラス定義のところに実装を書くこともできるのですが、クラス定義をヘッダファイルに書く場合は、クラス定義とその実装とを分離した方がコンパイル時間が短くなったり、オブジェクトファイルのサイズの肥大化を防ぐことができるようです。というのも、ヘッダファイルに実装も書いてしまうと、実装を修正しただけでそのヘッダファイルを取り込んだソースファイルをコンパイルし直さなければなりません。また、実装がヘッダファイルにある場合、include 文によって実装までもが取り込み先のソースファイルに展開されてしまいます。そのため、クラス定義と実装は分離しておくことが推奨されているようです(あえて取り込み先に埋め込む場合や、今回のように同じファイルにあるのであれば分離する必要は特にないようです)

ちなみに、自分自身のオブジェクトを示す this キーワードは C++ にもあります。ただし、 C++ の場合は自分自身のオブジェクトのポインタなので、自分自身のオブジェクトにあるメンバにアクセスする場合は、アロー演算子(->)を使わなければなりません。(Java と同様、メンバにアクセスする場合の this キーワードは省略可能です)

ところで、次のコンストラクタの文は何でしょうか。

Person::Person(const string& name, int age) : name(name), age(age) { }

「:」以降の「name(name), age(age)」は、「初期化リスト」と言って、コンストラクタに渡された引数をメンバ変数に設定する役割があります。

これを Java 風に書くと、次のようになります。

Person::Person(const string& name, int age) {
	this->name = name;
	this->age = age;
}

上の Java 風のやり方でも動作はするのですが、 C++ では初めに示したような「name(name), age(age)」というやり方が推奨されています。

というのも、Java 風の考えでいくと、メンバ変数の「string name」は string 型の参照を格納する変数に見えるのですが、 C++ では string 型のオブジェクトそのものを格納する変数を表します。つまり、 Java 風の書き方の場合、

  1. メンバ変数 name が初期化。
  2. メンバ変数 age が初期化。
  3. メンバ変数 name に引数 name のオブジェクトを代入。
  4. メンバ変数 age に引数 age の値を代入。

という形になります。これが初期化リストを使った場合は

  1. メンバ変数 name に引数 name のオブジェクトをコピー。
  2. メンバ変数 int に引数 age の値をコピー。

という順序で初期化が完了します。初期化リストを使うことで、効率よくメンバ変数を初期化できるわけですね。

それでは、コンストラクタの引数にある「const string& name」は何でしょうか。「&」の付いた型は参照型と呼ばれます。詳細はまた別の記事に書きますが、参照型には渡される変数の別名のような役割があります。つまり、この引数に渡される変数(やオブジェクト)を表す型になります。「string name」とした場合は値渡しとなって、コンストラクタの呼び出し元とコンストラクタ間でオブジェクトのコピーが行われますが、参照型の場合は行われず、そのまま扱えるイメージです。なお、const は、参照型として渡されたオブジェクトの内容を変えない(つまり、const の付けられたメンバ関数以外にはアクセスしない)という意味を表します。


DATE : 2006/09/18 (Mon)

とうとう本格的に BREW に手を出すことになったので、C++ の勉強を始めました。

(;´Д`)本当は Java ME の方を先にするつもりだったのですが、Java ME の件は立ち消えになってしまいました

Java と C はそれなりにやってきましたので、主にそれらと C++ との違いを中心にした記事を書く予定です。

(´・ω・) C の方の腕は完全になまってますが……

とりあえず、最低限 BREW の開発ができるほどの分量を理解できればいいのですが……(C++ の言語仕様の膨大さに弱腰気味です)

(;^ω^)まあ、BREW はいざとなれば C でも開発できるので、気楽にいこうと思います。

忍者ブログ [PR]
ブログ内検索
最近の状況
リンク
カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
使用許諾
最新コメント
(08/15)
(05/04)
(03/06)
(03/04)
(09/25)
最新トラックバック
ブログ内検索
最近の状況
リンク
カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
使用許諾
最新コメント
(08/15)
(05/04)
(03/06)
(03/04)
(09/25)
最新トラックバック