クラスのメンバに別のクラスや構造体の配列を入れるときの話

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
質問者A

クラスのメンバに別のクラスや構造体の配列を入れるときの話

#1

投稿記事 by 質問者A » 10年前

ゲームでマップ作りをしているのですが、マップの情報やそこにいるユニットの情報などをメンバ配列として保存することでUpdateとDrawの二つで使用できるようにしたいのです。そしてなおかつその情報は外部ファイルから入力できるようにしたいのです。単純に言えば

コード:

class A{
   private:
        AMap *p_a_map;
        Unit   *p_unit;
        bool  error;
   public:
          A(int number = 0);
          void Update();
          void Draw();
};

A::A(int number){

       char *str;

     switch(number){
         case 0:
            error = true;
            break;
        case 1:
            str = "map1.csv";
            break;
        //マップの数だけ同じ処理

         //ファイルのオープン

         a_map.aa =  //それぞれの項目を読み込み

        p_a_map = *a_map;

     } 
}
と言った形でやって後からメンバの中身を設定して、mainの中でUpdateとDrawを使う形にしたいのですけどこれだとコンストラクタと言え、ローカル変数なので実体が消えてしまいますよね?だからと言ってUpdate本文にa_mapを作ってもDrawの処理に言った時にマップデータ消えてしまうし…どのようにしたらいいのでしょうか?説明が下手ですみません。一部処理は勉強中なので省いています

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#2

投稿記事 by 質問者A » 10年前

p_a_map = *a_map;

じゃなくて

p_a_map = &a_map

でした

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#3

投稿記事 by みけCAT » 10年前

質問者A さんが書きました:これだとコンストラクタと言え、ローカル変数なので実体が消えてしまいますよね?
どのようにデータを読み込む領域を確保しているかがわからないので、わかりません。
消えるデータなら消えます(消えない保証はないです)し、消えないデータ(静的変数など)なら消えないはずです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#4

投稿記事 by みけCAT » 10年前

質問者A さんが書きました:どのようにしたらいいのでしょうか?
std::vector<適切な型>型のメンバ変数に読み込む、コンストラクタでnewなどを用いて動的に領域を確保してデストラクタで開放する、などいろいろな方法が考えられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#5

投稿記事 by 質問者A » 10年前

みけCAT さんが書きました:
質問者A さんが書きました:どのようにしたらいいのでしょうか?
std::vector<適切な型>型のメンバ変数に読み込む、コンストラクタでnewなどを用いて動的に領域を確保してデストラクタで開放する、などいろいろな方法が考えられます。
データメンバの段階で動的にメモリを確保するということでしょうか?
vectorもnewも関数内で行い、拡張してもstaticなしではメソッドが終わった時に拡張した値が消えてしまうのではないですか?
また、データメンバの段階で指定の大きさに動的に確保するにはどうすればいいのでしょうか?
static num のようなものを作り、mainで確保したい大きさをそこに入れてこれを利用してデータメンバ内で記述するのでしょうか
class A{
private:
static int num;
AMap *p_a_map = (AMap *)malloc(sizeof(AMap)*num);
}

読み出したファイルの指定するマップの大きさに合わせて作った動作用のAオブジェクト内のマップ配列の大きさを変えたいのです。その際、マップをそのAオブジェクト内においては消えずに使いまわせるようにしたいのです。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#6

投稿記事 by みけCAT » 10年前

質問者A さんが書きました:データメンバの段階で動的にメモリを確保するということでしょうか?
申し訳ないですが、よくわかりません。
質問者A さんが書きました:vectorもnewも関数内で行い、拡張してもstaticなしではメソッドが終わった時に拡張した値が消えてしまうのではないですか?
「vectorを行う」「拡張」の意味がよくわかりませんが、newで確保した領域は一般にはメソッドが終わっても(確保したブロックを抜けても)勝手に消えません。
メモリリークを起こす可能性があるのはそのためです。
質問者A さんが書きました:また、データメンバの段階で指定の大きさに動的に確保するにはどうすればいいのでしょうか?
不可能と考えるのがいいかもしれません。
質問者A さんが書きました:static num のようなものを作り、mainで確保したい大きさをそこに入れてこれを利用してデータメンバ内で記述するのでしょうか
不可能ではないと思いますが、かなり不自然な仕様だと思います。
質問者A さんが書きました:class A{
private:
static int num;
AMap *p_a_map = (AMap *)malloc(sizeof(AMap)*num);
}
こうしてC++11モードでコンパイルすると通りましたが、個人的には素直にコンストラクタの引数でデータを渡したほうがいいと思います。

コード:

#include <cstdio>
#include <cstdlib>

typedef int AMap;

class A{
	private:
		static int num;
		//AMap *p_a_map = (AMap *)malloc(sizeof(AMap)*num);
		int n = num;
	public:
		void print() const {
			printf("%d\n",n);
		}
		static void set_num(int n_) {
			num = n_;
		}
}

;int A::num;

int main(){
	A::set_num(10);
	A a;
	A::set_num(5);
	A b;
	a.print();
	b.print();
	return 0;
}
質問者A さんが書きました:読み出したファイルの指定するマップの大きさに合わせて作った動作用のAオブジェクト内のマップ配列の大きさを変えたいのです。その際、マップをそのAオブジェクト内においては消えずに使いまわせるようにしたいのです。
適当なサンプルを書いてみました。

コード:

#include <cstdio>

typedef char MapChip;
typedef int Unit;

class AMap {
	private:
		int width, height;
		int next_map;
		MapChip *m;
	public:
		AMap(int w = 1, int h = 1) {
			// w, hの値のエラーチェックは省略
			width = w;
			height = h;
			m = new MapChip[w * h];
		}
		AMap(const AMap& a) {
			width = a.width;
			height = a.height;
			next_map = a.next_map;
			m = new MapChip[width * height];
			for (int i = 0; i < width * height; i++) m[i] = a.m[i];
		}
		~AMap() {
			delete[] m;
		}
		AMap& operator=(const AMap& a) {
			width = a.width;
			height = a.height;
			next_map = a.next_map;
			delete[] m;
			m = new MapChip[width * height];
			for (int i = 0; i < width * height; i++) m[i] = a.m[i];
			return *this;
		}
		const MapChip& at(int x, int y) const {
			return m[width * y + x];
		}
		MapChip& at(int x, int y) {
			return m[width * y + x];
		}
		int get_width() const {
			return width;
		}
		int get_height() const {
			return height;
		}
		int get_next_map() const {
			return next_map;
		}
		void set_next_map(int n) {
			next_map = n;
		}
		static AMap load(const char *data) {
			// ダミー
			AMap map = AMap(10, 10);
			const char *p = data;
			for (int y = 0; y < 10; y++) {
				for (int x = 0; x < 10; x++) {
					map.at(x, y) = *(p++);
					if (*p == '\0') p = data;
				}
			}
			return map;
		}
};

class A {
	private:
		AMap *p_a_map;
		Unit *p_unit;
		bool error;
	public:
		A(int number = 0);
		~A();
		void Update();
		void Draw();
};

A::A(int number) {
	const char *str;

	p_a_map = NULL;
	p_unit = NULL;
	error = false;

	switch(number) {
		case 0:
			error = true;
			break;
		case 1:
			str = "map1.csv";
			break;
		default:
			error = true;
			break;
	}

	if(!error) {
		// 読み込み処理のかわり
		int map_num = 3;
		p_a_map = new AMap[map_num];
		p_a_map[0] = AMap::load("asumikana");
		p_a_map[0].set_next_map(2);
		p_a_map[1] = AMap::load("kitamuraeri");
		p_a_map[1].set_next_map(-1);
		p_a_map[2] = AMap::load("kugimiyarie");
		p_a_map[2].set_next_map(1);
		(void)str; // 警告避け
	}
}

A::~A() {
	if(p_a_map != NULL) delete[] p_a_map;
}

void A::Update() {
}

void A::Draw() {
	puts("++++++++++");
	if (p_a_map != NULL) {
		for (int map = 0; map >= 0; map = p_a_map[map].get_next_map()) {
			for (int y = 0; y < p_a_map[map].get_height(); y++) {
				for (int x = 0; x < p_a_map[map].get_width(); x++) {
					putchar(p_a_map[map].at(x, y));
				}
				putchar('\n');
			}
			puts("=====");
		}
	}
	puts("----------");
}

int main(void) {
	A a1 = 0;
	A a2 = 1;
	puts("draw a1");
	a1.Draw();
	puts("draw a2");
	a2.Draw();
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

hide

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#7

投稿記事 by hide » 10年前

独特な表現が多いようですが、独学ですか?

プログラミングを掲示板で教える上で文章が正しく通じないのは困りますので・・・。

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#8

投稿記事 by 質問者A » 10年前

hide さんが書きました:独特な表現が多いようですが、独学ですか?

プログラミングを掲示板で教える上で文章が正しく通じないのは困りますので・・・。
ええっと。C++は独学です。CとJavaをちょこっと習いました。変な表現になってしまうっているのは多分自分が簡単なことを深く考えすぎてわけがわからなくなってしまうタイプだからだと思います。
<1>

コード:

  int*  getA(){
       int a = 7;
       return &a
   }

   int main(){
       int *a;
       a = getA();
       *a = 3;
    }
<2>

コード:

class A{
   pirivate:
       B *p_b;
   public:
       A();
       int getB();
}

A::A(){

   B b;
   p_b = &b;
}

int A::getB(){

   return p_b->i;

}


<3>
class A{
   pirivate:
       B *p_b;
   public:
       A();
       int getB();
}

A::A(){

   p_b = new B();

}

int A::getB(){

   return p_b->i;

}

C言語で<1>のように書くと確かリターンの時に値が解放されているため、aの中に入っているアドレスの指し示す先がなくなっているためエラーになると教わったんですが、これをC++を勉強しながら、マップを作っている時に思い出して<1>の例と<2>の例はメソッドとコンストラクタ、int型と自作クラスの違いだけでコンストラクタやメソッドの内部で宣言した変数のアドレスを入れているということで変わらないのではないかと考えてしまったんです。ならば<2>でも<1>と同じエラーになってしまうのではないかと。
今回<3>の例で実行できるということはnew B()によって動的にメモリを確保することでコンストラクタが終わっても解放されないため、p_bに入っているアドレスを使って対象を弄れるということであっていますか?動的確保をしていない<2>の例ではやはりエラーになるっと言う解釈であっていますか?あとvectorはB bみたいな扱いではなくて new B()と言ったように追加する配列を確保しているということでいいのでしょうか?

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#9

投稿記事 by みけCAT » 10年前

質問者A さんが書きました:今回<3>の例で実行できるということはnew B()によって動的にメモリを確保することでコンストラクタが終わっても解放されないため、p_bに入っているアドレスを使って対象を弄れるということであっていますか?動的確保をしていない<2>の例ではやはりエラーになるっと言う解釈であっていますか?
スペルミスを除けば、多分あっていると思います。
質問者A さんが書きました:あとvectorはB bみたいな扱いではなくて new B()と言ったように追加する配列を確保しているということでいいのでしょうか?
微妙な気がします。確かにstd::vectorは配列を確保するはずですが、vectorならば必ず消えないというわけではなく、

コード:

#include <cstdio>
#include <vector>

std::vector<int> *hoge(void) {
	std::vector<int> a;
	a.push_back(346);
	return &a;
}

int main(void) {
	std::vector<int> *a = hoge();
	printf("%d\n", a->at(0));
	return 0;
}
のようなコードを書けば当然消えます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#10

投稿記事 by 質問者A » 10年前

みけCAT さんが書きました:
質問者A さんが書きました:今回<3>の例で実行できるということはnew B()によって動的にメモリを確保することでコンストラクタが終わっても解放されないため、p_bに入っているアドレスを使って対象を弄れるということであっていますか?動的確保をしていない<2>の例ではやはりエラーになるっと言う解釈であっていますか?
スペルミスを除けば、多分あっていると思います。
質問者A さんが書きました:あとvectorはB bみたいな扱いではなくて new B()と言ったように追加する配列を確保しているということでいいのでしょうか?
微妙な気がします。確かにstd::vectorは配列を確保するはずですが、vectorならば必ず消えないというわけではなく、

コード:

#include <cstdio>
#include <vector>

std::vector<int> *hoge(void) {
	std::vector<int> a;
	a.push_back(346);
	return &a;
}

int main(void) {
	std::vector<int> *a = hoge();
	printf("%d\n", a->at(0));
	return 0;
}
のようなコードを書けば当然消えます。
vectorであってもローカル関数で宣言してそのアドレスを使う場合はだめなんですね。メンバ変数で変数自体を作っておけばローカル関数内でpush_back()で値を追加しても消えないということですかね?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#11

投稿記事 by softya(ソフト屋) » 10年前

プログラムを組むときにはバグが出来るだけ発生しない事を考えたほうが良いですね。
安全にvectorで扱うには、
1.ポインタで扱う場合はvectorよりも変数の寿命が長いことが保証されていこと。
2.インスンタンスの生成や消滅時に利用できないvectorが発生する恐れがないようにvectorの寿命と同一が出来るだけ望ましい。
3.生成順番を考慮しないと動かないようなプログラムはメンテ性が悪くなる(バグが発生しやしい)。
を頭においてください。
ローカル変数やメンバ変数は、クラス外に取り出されるなら避けましょう。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#12

投稿記事 by みけCAT » 10年前

std::vectorでは要素数の拡張時などにデータのコピーが発生するので、特にポインタを使う場合は誤って消さないように注意が必要です。
×コピー時にポインタの値のみをコピー(シャローコピー)→デストラクタでコピー元のポインタが指すデータを開放する→コピー先のデータ中のポインタも開放した場所を指している(死亡)
○コピー時にポインタが指す内容をコピー(ディープコピー)デストラクタでコピー元のポインタが指すデータを開放する→コピー先のデータ中のポインタは新たに確保した場所を指しているのでOK
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#13

投稿記事 by 質問者A » 10年前

みけCAT さんが書きました:std::vectorでは要素数の拡張時などにデータのコピーが発生するので、特にポインタを使う場合は誤って消さないように注意が必要です。
×コピー時にポインタの値のみをコピー(シャローコピー)→デストラクタでコピー元のポインタが指すデータを開放する→コピー先のデータ中のポインタも開放した場所を指している(死亡)
○コピー時にポインタが指す内容をコピー(ディープコピー)デストラクタでコピー元のポインタが指すデータを開放する→コピー先のデータ中のポインタは新たに確保した場所を指しているのでOK
シャローコピーは同じものを指すコピーでディープコピーは別々のものを指すコピーですね。vectorのコピーはポインタを使うとシャローになってしまうのでポインタを使わない方法でコピーを取ればOKということですか

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#14

投稿記事 by みけCAT » 10年前

質問者A さんが書きました:vectorのコピーはポインタを使うとシャローになってしまうのでポインタを使わない方法でコピーを取ればOKということですか
「vectorのコピーはポインタを使うとシャローになってしまう」というのは違うと思いますが、
ポインタを(プログラマからの見た目で)使わないというのは無効なデータが含まれないための十分条件である(必要条件とは限らない)気がします。
オフトピック
x86のCPUレベルでは、レジスタに収まる小さいデータでない限りはポインタを使わないとコピーできないと思いますが…
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#15

投稿記事 by 質問者A » 10年前

みけCAT さんが書きました:
質問者A さんが書きました:vectorのコピーはポインタを使うとシャローになってしまうのでポインタを使わない方法でコピーを取ればOKということですか
「vectorのコピーはポインタを使うとシャローになってしまう」というのは違うと思いますが、
ポインタを(プログラマからの見た目で)使わないというのは無効なデータが含まれないための十分条件である(必要条件とは限らない)気がします。
オフトピック
x86のCPUレベルでは、レジスタに収まる小さいデータでない限りはポインタを使わないとコピーできないと思いますが…
違うんですか?むむむ…なんかわけがわからなくなってきました。

…もう一度、本などで勉強し直したほうが良いかもしれません…でも言語の勉強本を見てもこういう風にすればいいと言うのはよく書いてあるんですけど、こういう風にするとこういう風になるからダメだよってことが詳しく書いてあるの少ないんですよね…。

そう言ったことをどう勉強すればいいのでしょうか?おすすめ本があれば教えていただけますか?

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#16

投稿記事 by usao » 10年前

オフトピック
std::vectorなどは便利だけれども,
「std::vectorもどきを自分で書ける = なんとなくvectorの実装ってこういうことになってるんだろうな,という想像がつく」
くらいになってから使う方がいいと思う.個人的には.

最初はnewとdelete等を使ってなんでもかんでも自前で生存期間を管理するように書いてみればどうでしょう.

>こういう風にするとこういう風になるからダメだよってこと

本などで読んで理解できればそれでいいですが,例えば,

コード:

//実行時に要素数を決定できるint配列 みたいな「つもり」でこれを書いたとして…
class MyArray
{
public:
  MyArray(){  m_Array = nullptr;  m_size=0;  }
  ~MyArray(){  delete[] m_Array;  }

  void resize( size_t size )
  {
    delete[] m_Array;
    m_Array = new int[ size ];
    m_size = size;
  }

  int &operator[]( int i ){	return m_Array[i];	}
  int operator[]( int i ) const {	return m_Array[i];	}
  size_t size() const {	return m_size;	}

private:
  int *m_Array;
  size_t m_size;
};

//
int main()
{
  MyArray A1;
  {
    MyArray A2;
    A2.resize( 10 );
    A1 = A2;  //おおっと…
  }

  return 0;
}
みたいなこととか,一度実際に経験してみた方が身に染みて理解できるかも?

質問者A

Re: クラスのメンバに別のクラスや構造体の配列を入れるときの話

#17

投稿記事 by 質問者A » 10年前

usao さんが書きました:
オフトピック
std::vectorなどは便利だけれども,
「std::vectorもどきを自分で書ける = なんとなくvectorの実装ってこういうことになってるんだろうな,という想像がつく」
くらいになってから使う方がいいと思う.個人的には.

最初はnewとdelete等を使ってなんでもかんでも自前で生存期間を管理するように書いてみればどうでしょう.

>こういう風にするとこういう風になるからダメだよってこと

本などで読んで理解できればそれでいいですが,例えば,

コード:

//実行時に要素数を決定できるint配列 みたいな「つもり」でこれを書いたとして…
class MyArray
{
public:
  MyArray(){  m_Array = nullptr;  m_size=0;  }
  ~MyArray(){  delete[] m_Array;  }

  void resize( size_t size )
  {
    delete[] m_Array;
    m_Array = new int[ size ];
    m_size = size;
  }

  int &operator[]( int i ){	return m_Array[i];	}
  int operator[]( int i ) const {	return m_Array[i];	}
  size_t size() const {	return m_size;	}

private:
  int *m_Array;
  size_t m_size;
};

//
int main()
{
  MyArray A1;
  {
    MyArray A2;
    A2.resize( 10 );
    A1 = A2;  //おおっと…
  }

  return 0;
}
みたいなこととか,一度実際に経験してみた方が身に染みて理解できるかも?
コピーコンストラクタがない動作を経験してみるってことですね

閉鎖

“C言語何でも質問掲示板” へ戻る