クラスの継承について

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

クラスの継承について

#1

投稿記事 by coc » 10年前

管理人様のhttp://dixq.net/g/sp_06.htmlを参考に現在プログラムしています。
このサイト内で
Game.h

コード:

#pragma once

#include "BaseScene.h"
#include "ISceneChanger.h"

//ゲーム画面クラス
class Game : public BaseScene {

public :
    Game::Game(ISceneChanger* changer);
    void Initialize() override;    //初期化処理をオーバーライド。
    //void Finalize() override;        //終了処理をオーバーライド。
    void Update() override;        //更新処理をオーバーライド。
    void Draw() override;            //描画処理をオーバーライド。

};
のGameクラスを継承してPlayerクラスを作ろうとしています

Player.h

コード:

#pragma once

#include "Game.h"

//ゲーム画面クラス
class Player : public Game {

public :
    //Player::Player(ISceneChanger* changer);
    void Initialize() override;    //初期化処理をオーバーライド。
    void Finalize() override;        //終了処理をオーバーライド。
    void Update() override;        //更新処理をオーバーライド。
    void Draw() override;            //描画処理をオーバーライド。

};
この時Game.cpp内のInitで

コード:

//初期化
void Game::Initialize(){
    mImageHandle = LoadGraph("images/Scene_Game.png");    //画像のロード
    //プレーヤー情報の初期化
    Player::Initialize();
    //他にも関連した初期化関数を呼び出していく
}
という形で実装していきたいのですが、クラス継承先のメンバ関数は継承元からは参照出来ないためどの様に組めば良いのでしょうか?
↑の形ではなくとも、一般的な構造を教えて頂けると助かります。

beatle
記事: 1281
登録日時: 12年前
住所: 埼玉
連絡を取る:

Re: クラスの継承について

#2

投稿記事 by beatle » 10年前

うーん。継承の意味を勘違いしているのではないでしょうか?

オブジェクト指向で継承というと、普通は「is-a関係」が成り立つ場合に使います。
この場合で言うと「Player is a Game」、日本語で言えば、「プレイヤーはゲームである」です。
多分違いますよね?
教科書的な例では「人間は哺乳類である」とか「三角形は図形である」とかでしょうか。

is-a関係の重要なことは、子クラスは親クラスとして見なせる、ということです。
人間は哺乳類ですので、哺乳類に成り立つことは人間にも成り立ちます。
三角形は図形ですので、図形にできる操作(色を付ける、移動する)は三角形にも適用できます。

そして、継承では親クラスは子クラス特有の情報を使わないのが、良いプログラムにするときの原則です。
親クラスは子クラスを使ってもいいですが、あくまで親クラスとして扱います。
図形は他の図形を扱ってもよいですが、それはあくまで「図形」の範囲で。三角形に特有な性質(3つの角度)などを図形クラスで使ってはいけません。

ということで、PlayerがGameを継承することは間違いじゃないでしょうか?
親(継承元)から子(継承先)を使わないことが原則ですから、ご質問の方向性は修正した方がいいと思います。

coc

Re: クラスの継承について

#3

投稿記事 by coc » 10年前

ご返信有難うございます!

今一まだ上手く理解出来てなく申し訳ないのですが
Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる

これは問題無いでしょうか
Player is a BaseDataと言われると違和感があるのでまだ改善の余地がありそうですが…。

またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
オブジェクト指向から離れてしまっている気がして心配です

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

Re: クラスの継承について

#4

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

名前をBaseDataクラスではなく、例えばCharacterクラスにする方がいい気がします。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
usao
記事: 1889
登録日時: 11年前

Re: クラスの継承について

#5

投稿記事 by usao » 10年前

やりたいことは単にこういうことなのだろうか…?

コード:

class Player
{
public:
  void Init();  //何かしらPlayerに必要な初期化処理を行う
};

//
class Game
{
public:
  void Init()
  {
    //何かしらGameに必要な初期化処理を行う
    ...
    //その一環として,Playerの初期化処理を行う(Playerに初期化処理を行わせる)
    m_PlayerInst.Init();
  }
private:
  Player m_PlayerInst;  //Playerインスタンス.とりあえずこの例ではGameがメンバとして保持.
};
オフトピック
>オブジェクト指向から離れてしまっている気がして心配です
個人的には,"オブジェクト指向がどうこう" とかいう言葉に囚われすぎずに,もっと気を楽に持ったほうが良い気がします.
どうしてもそういう心配をしたいのだとしても,もっと後からしても遅くないかもしれませんし.

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

Re: クラスの継承について

#6

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

オーバーライド前提なら、純粋仮想関数で良いんじゃないかと言う気もします。
その場合は、共通処理は別のメンバ関数で行う必要ありますですね。

>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?

とにかく、親から子のクラスやメンバ関数は見えないし見ては行けないです。
そもそもこういう仕様の必要があるなら継承を使うより委譲を使うべきところなのでは?
【補足】 usaoさんが委譲の例を書いてましたね。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

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

Re: クラスの継承について

#7

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

横入り(?)失礼します。
softya(ソフト屋) さんが書きました:>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?

とにかく、親から子のクラスやメンバ関数は見えないし見ては行けないです。
そもそもこういう仕様の必要があるなら継承を使うより委譲を使うべきところなのでは?
【補足】 usaoさんが委譲の例を書いてましたね。
ここでsoftya(ソフト屋)さんが言っている「親」「子」とは、どういう性質をもつものですか?
cocさんの投稿(No: 3)の自分の解釈(Javaで書くと)

コード:

class Game {
    // hoge
}

class BaseData {
    // foo
}

class Player extends BaseData {
    // fuga
}

class mob extends BaseData {
    // piyo
}
は間違っているでしょうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: クラスの継承について

#8

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

あれ? 最初のコードはPlayer はGameを継承しているんで、そのつもりで書いたんですが話の流れとズレてますか?

編集を間違ったので修正。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

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

Re: クラスの継承について

#9

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

softya(ソフト屋) さんが書きました:あれ? 最初のコードはPlayer はGameを継承しているんで、そのつもりで書いたんですが話の流れとズレてますか?
coc さんが書きました:またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
この発言の「この場合」は、最初のコードではなく
coc さんが書きました:Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる
という場合だと解釈しましたが、話の流れとズレているでしょうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: クラスの継承について

#10

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

失礼しました。
Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる
これが前提ですね。
とすると、

>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?

委譲しないと呼び出せないですね。
つまり、GameがPlayerのインスタンスを持つって事です。これはusaoさんの書かれたとおりです。
Initをstaticにしても良いですが、こはもうオブジェクト指向的に書きたという意図から外れていくばかりだと思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

coc

Re: クラスの継承について

#11

投稿記事 by coc » 10年前

現在皆様のご意見を元にBaseDataをBaseCharacterとして

コード:

#pragma once


//キャラのベースとなるクラス
class CBaseCharacter{

private:

public :
    CBaseCharacter();
    void Initialize();    //初期化処理
    //void Finalize();        //終了処理
    void Update();        //更新処理
    void Draw();            //描画処理

};
このようにヘッダを作り、Gameからusaoさんの提示して下さったコードを参考に
Game.h

コード:

#pragma once

#include "BaseScene.h"
#include "ISceneChanger.h"
#include "BaseCharacter.h"

//ゲーム画面クラス
class Game : public BaseScene {

private:
	CBaseCharacter m_BaseCharacter;

public :
    /*Game::*/Game(ISceneChanger* changer);
    void Initialize();    //初期化処理をオーバーライド。
    //void Finalize() override;        //終了処理をオーバーライド。
    void Update() override;        //更新処理をオーバーライド。
    void Draw() override;            //描画処理をオーバーライド。

};
としてGame::Init内でCBaseのInitを呼び出す事に成功しました。

またPlayer.hは

コード:

#pragma once

#include "BaseCharacter.h"

//プレーヤーのクラス
class CPlayer : public CBaseCharacter {

public :
    //Player::Player();
    void Initialize();    //初期化処理。
    void Finalize();        //終了処理。
    void Update();        //更新処理。
    void Draw();            //描画処理。

};
としてBaseCharacterを継承しています。
この状態でCPlayerのInitを呼び出す一般的な方法を教えて頂けると助かります。

最初CBaseCharacterをインタフェースクラスにして初期化などを純粋仮想関数に、ヘッダにenumで{Player,Mob・・・}などを記載しておきfor文などでじゅんぐりに呼び出す方法を考えましたが、CBaseCharacterをインタフェースクラスにするとメンバ変数が置けなくなるためこれでは駄目だなと。
次にCPlayerもCBaseCharacterからの継承でなく委譲関係にしたら問題無いと考えましたが、これまで委譲にするともう全部委譲になってしまうような気がして。

何か単純な方法に気付けていないだけな気がしてなりませんが、一般的に継承先から継承元を参照するのでそもそもGameとCBaseCharacterを委譲関係にするのではなく、GameとCPlayerやMobなどを委譲関係にすべきなのでしょうか?

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

Re: クラスの継承について

#12

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

この形だとCBaseCharacterのインスタンスとCPlayerのインスタンスは一切の関わりがないのでCPlayer::Initは呼び出す事は不可能ですね。

>何か単純な方法に気付けていないだけな気がしてなりませんが、一般的に継承先から継承元を参照するのでそもそもGameとCBaseCharacterを委譲関係にするのではなく、GameとCPlayerやMobなどを委譲関係にすべきなのでしょうか?

やりたいことを説明出来ていないので、みなさんの回答がcocさんの望むものとズレるんだと思います。
現状で出来なくて困ることはなんでしょうか?
例えば、GameクラスでCBaseCharacter ではなくCPlayer のインスタンスを持つと出来ないことは?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
usao
記事: 1889
登録日時: 11年前

Re: クラスの継承について

#13

投稿記事 by usao » 10年前

ひょっとして 仮想関数 についてちゃんと調べられると良いのかも.

>CBaseCharacterをインタフェースクラスにするとメンバ変数が置けなくなるためこれでは駄目だなと
別に基底クラス側を 「純粋仮想関数しか持たないクラス」 にすることに
こだわらなくてもよいのではないかと思いますが,どうなんでしょう.

コード:

//やや謎な例ですが,別に仮想クラスでもメンバ変数を持ちたければ持てばいい.

//武器を用いてターゲットを殴るクラス用の基底.
//実際に殴りかかる人を表すクラスはこれから派生し,
//オリジナリティあふれる殴り方を仮想関数Attack()に実装する.
class CAttackerBase
{
public:
  virtual ~CAttackerBase() {}
public:
  //現在の武器を用いてでターゲットを殴る.
  //実際にどのように殴るのか{横殴り?打ち下ろし?etc}は派生先で実装する.
  //[戻り値]:無事に殴れたらtrueを返すこと.
  virtual bool Attack( Tgt & ) = 0;

  //Attackerは武器を装備できる
  void EquipWeapon( const Weapon &rWeapon ){  m_CurrentWeapon = rWeapon;  }
  const Weapon &GetCurrentWeapon() const {  return m_CurrentWeapon;  }
private:
  Weapon m_CurrentWeapon;  //現在装備中の武器
};
一応継承してるコードを追記.
► スポイラーを表示
最後に編集したユーザー usao on 2014年2月05日(水) 16:44 [ 編集 2 回目 ]

coc

Re: クラスの継承について

#14

投稿記事 by coc » 10年前

現状はまだ全然中身が無いために、CPlayerのインスタンスを持たせれば問題無いです。
ただこれからどんどん継承先のクラスが多岐に渡り増えていくと思われるため、クラス継承の枝先を悉くGameに集めてしまう事は避けたいと思いました。

頭の中の考えでは
Game → 3つほど大まかなオブジェクトで分類し、それぞれの初期化など関数を呼び出し → 3つのオブジェクト関数の中から更に枝分かれした関数の呼び出し → ・・・・
という形です。

恐らくこの考えがおかしいから皆様と食い違っているのだと思います。

一般的にこのように枝分けされた場合、枝先を呼び出していくにはどのように記述していけば宜しいのでしょうか

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

Re: クラスの継承について

#15

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

えーと、やっぱり良くわからないです。

こういうのが出来たらOKなんですかね?

コード:

#include<iostream>

class CBaseCharctor {
public : 
    CBaseCharctor::CBaseCharctor() {
		std::cout << "CBaseCharctor()\n";
	}
    virtual void Initialize() {
		std::cout << "CBaseCharctor::Initialize()\n";
	}
};

class CPlayer : public CBaseCharctor {
 
public : 
    CPlayer::CPlayer() {
		std::cout << "CPlayer()\n";
	}
    void Initialize() {
		CBaseCharctor::Initialize();
		std::cout << "CPlayer::Initialize()\n";
	}
 
};

int main() {
	CPlayer player;
	
	player.Initialize();
	
	return 0;
}
それともこっち?

コード:

#include<iostream>

class CBaseCharctor {
public : 
    CBaseCharctor::CBaseCharctor() {
		std::cout << "CBaseCharctor()\n";
	}
    virtual void Initialize() {
		std::cout << "CBaseCharctor::Initialize()\n";
	}
};

class CPlayer : public CBaseCharctor {
 
public : 
    CPlayer::CPlayer() {
		std::cout << "CPlayer()\n";
	}
    void Initialize() {
		CBaseCharctor::Initialize();
		std::cout << "CPlayer::Initialize()\n";
	}
 
};

int main() {
	CBaseCharctor *baseChar;
	
	baseChar = new CPlayer();
	
	baseChar->Initialize();
	
	return 0;
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: クラスの継承について

#16

投稿記事 by ISLe » 10年前

質問者さんは、実行時にどのタイミングでオブジェクトを生成するのかという点についても曖昧なのではないでしょうかね。
各オブジェクトのライフサイクルをガントチャートふうに図に描いてみたら分かりやすくなるかもしれません。

Gameクラスが継承しているのはBaseSceneクラスなので、Gameクラスはシーンを表しているのでしょう。
わたしなら、プレイヤークラスの生成・管理はさらに上位のクラスで行うようにすると思います。

新・ゲームプログラミングの館で言うとSceneMgrになってしまいますが、シーンの変化を検知してオブジェクトの生成・破棄を行うオブジェクトマネージャを新設するのが理想です。

Initialize,Finalizeはわざわざ呼び出さなくてもそれぞれコンストラクタ,デストラクタを使えば良いと思います。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: クラスの継承について

#17

投稿記事 by ISLe » 10年前

館のコードに合わせるなら、Gameクラスの中でさらにシーンを細分化する方向がありますね。
そうしたら、プレイヤークラスは単純にGameクラスのメンバで良いですね。

コード:

class Game : public BaseScene {
private:
    CPlayer m_Player;
public :
    Game(ISceneChanger* changer);
    void Initialize() override;
    void Finalize() override;
    void Update() override;
    void Draw() override;
};

Game::Game(ISceneChanger* changer) : m_Player()
{
}

void Game::Initialize()
{
    m_Player.Initialize();
}
No.5でusaoさんが書いたのと同じことですけど。

メンバが増えていくことを懸念されているんでしたっけ。

coc

Re: クラスの継承について

#18

投稿記事 by coc » 10年前

ISLeさんのおっしゃる様にGameクラスを更に細分化していく方向に決めました。

それとは別ながらクラスの継承についての問題なのですが、

GameクラスでPlayerとMob[2]のインスタンスを生成
BaseCharaから継承したPlayerクラスに座標などの情報を保持
BaseCharaから継承したMob[2]クラスに座標などのを保持(for文でGameから複数回呼び出す事で初期化)

この時BaseCharaに当たり判定を行うPrivateな関数を用意する予定でした。(引数はVector型の座標を二セット)
しかし実際組んでみると今更ながらPlayerとMobクラスは互いに関係性を持たないため、自身の座標は送れても相手の座標を見る事が出来ないという現実に気付きました。

ISLeさんのおっしゃられた様に紙に関係図を書いてみたのですが、CameraやStageクラスを何処に配置するかについても困っております。
(今はどちらもPlayerと委譲関係にしてありますが)

このような状態
      BaseChara
         ↓
Game → Player ← Camera,Stage
     → Mob

クラスの組み方がさっぱりです・・・

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

Re: クラスの継承について

#19

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

GameがPlayerとMobを知っているので、当たり判定もGameが呼び出して行えばよいでしょう。
result = collision(Player.GetPoint(),Mob.getPoint());

Camera,StageもGameが管理して良いと思います。Gameってゲームシーンの全体を統括するシーンマネージャなんですよね?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
usao
記事: 1889
登録日時: 11年前

Re: クラスの継承について

#20

投稿記事 by usao » 10年前

>CameraやStageクラスを何処に配置するか

とりあえず 現状では,
「Gameがアクティブなシーンになっている間だけ必要なもの」
は全て Gameがメンバ変数として持つ という形を考えたら楽になりませんか?(気持ち的な意味で)

>当たり判定

CBaseCharactor(の派生)同士であれば,当たり判定を行う方法が共通 とかいう状況であれば

コード:

class CBaseCharactor
{
  ...
public:
  //CollisionCheckData は実際は何になるのかわからないけど
  //(例えば 矩形だとか,中心座標だとか?) とにかく当たり判定計算に必要なデータ.
  virtual CollisionCheckData GetCollisionCheckData() const = 0;
};

//当たり判定関数
//※戻り値の型は何がいいのかよくわからんけど
int CollisionCheck( const CBaseCharactor &rObj1, const CBaseCharactor &rObj2 )
{
  CollisionCheckData D1 = rObj1.GetCollisionCheckData();
  CollisionCheckData D2 = rObj2.GetCollisionCheckData();
  D1とD2を使って判定を行い,その結果を返す
}
みたいなことでよいのではないかなぁ.
オフトピック
>CameraやStageクラスを何処に配置するか

とかは,「とりあえず」「どこか(自分がここかな?と思う場所あたり)」に配置してやってみて
その結果,「ここにあると不便」とか「ここじゃどうしようもない」だとか「ここにあればすんなりいって良い予感」とかいう
手応え(?)が得られたならば,それに沿って修正するなりそのままいくなりを決めていければ良いと思うのです.
というのは,経験のある方々からの 「良い(ように見える)」回答 を参考にするにしても,
それが「何故良いのか」という理由の部分を掴むことが重要だと思うので.
そのためには,「これこれこういう考えのもとで,実際こうしてみたんだけど,それだと××だった」という試行錯誤というか
段階があった方がいいんじゃないかな…とか.(余計なお世話かもですが.)

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: クラスの継承について

#21

投稿記事 by ISLe » 10年前

クラスの組み方ではなくて、オブジェクトの関係を整理するほうが先決なのでは。

同じクラスを継承すれば勝手に連携できるようになる、と考えているならば間違いです。

上位にマネージメントするクラスがあって、そのクラスとやり取りするクラスがある。
そのやり取りのインターフェースを共通化する、というのが基底クラスの役割りです。
やり取りを共通化する見通しが立ってないのに形だけ基底クラスを用意しても意味がありません。


参考になるか分かりませんが、この掲示板でも過去にこの手の設計の話題が何度か出ています。
『天の声』とか『神の視点』で過去ログを検索してみてください。

coc

Re: クラスの継承について

#22

投稿記事 by coc » 10年前

皆さんご返事有り難う御座います。
おっしゃって頂いた様に、取り敢えず組んでみて駄目だったら改善していく手法で今回はチャレンジしてみます。

閉鎖

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