複数のクラスに共通な関数を定義する方法を知りたいです

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
笹山一郎吉岡
記事: 7
登録日時: 10年前

複数のクラスに共通な関数を定義する方法を知りたいです

#1

投稿記事 by 笹山一郎吉岡 » 10年前

コード:

class ClassBase
{
protected:
    int x, vel, acc;
public:
    virtual void Move() = 0;
};

class ClassA : public ClassBase
{
public:
    void FuncA()
    {
        // 実装
    }
    void Move()
    {
        vel += acc;
        x += vel;
    }
};

class ClassB : public ClassBase
{
public:
    void FuncB()
    {
        // 実装
    }
    void Move()
    {
        vel += acc;
        x += vel;
    }
};

class ClassC : public ClassBase
{
private:
    int time;
public:
    void FuncC()
    {
        // 実装
    }
    void Move()
    {
        x = vel*time +0.5*acc*time*time;
    }
};

class ClassD : public ClassBase
{
public:
    void FuncC()
    {
        // 実装
    }
    void Move()
    {
        x++;
    }
};
上記のとき、ClassA と ClassB の全く同じ内容の関数 Move() をコピペ以外の方法で実装する方法はあるでしょうか。
ClassA と ClassB で共通な、そのクラス自身のメンバ変数を操作する関数をどこかで定義しておき、
ClassA と ClassB に実装するということです。できれば継承は ClassBase だけにしたいですが、
2階層以上の継承や多重継承も選択肢に入れて、最もよさそうな方法を教えてください。
ClassA,ClassB に共通と書きましたが、その他の ClassF,ClassZ でも共通にする必要性があるという条件でお願いします。

私は以下の方法を思いつきました。


・MoveFuncs というクラスを作り、DoAB(int* x, int vel, int acc) として関数を実装、
 ClassA,ClassB では MoveFuncs クラスを包含して、Move(){ MoveFuncs.DoAB(&x, vel, acc); } として Move() を定義

・上記と似たような感じで、Strategy パターンを使用する

・フレンド関数で実装する

・ClassBase を継承した ClassBaseForAB という ClassA と ClassB のための Move() が実装されたクラスを継承する

・上記と似たような感じで多重継承で実装する


もしくは以下の場合であったらその理由も教えてください。


・他の言語なら可能

・コピペが一番

・複数のクラスに共通な関数を実装しようとするのはおかしい

・そもそも考え方が間違ってる


よろしくお願いいたします。



[1] 質問文
 [1.1] 自分が今行いたい事は何か:
     複数のクラスに共通な関数を定義する方法を知りたい

 [1.2] どのように取り組んだか
     インターネットや本で、包含、委譲、継承、デザインパターンについて調べた

 [1.3] どのようなトラブルで困っているか
     共通な関数を定義するための方法は種々あるが、継承をなるべく少なく、
     また、なるべく単純な実装、もしくは熟練者から見て適切な実装を知りたいが、
     自分の判断を基準にできない

[2] 環境  
 [2.1] OS : Windows7
 [2.2] コンパイラ名 : VC++ 2012

[3] その他
 ・どの程度c++言語を理解しているか
     1年程度。何度か簡単な制御・計測ソフトの作成をしている

KORYUOH
記事: 44
登録日時: 12年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#2

投稿記事 by KORYUOH » 10年前

Moveを純粋仮想関数にしないで必要なクラスのみがオーバーライドするようにするのではだめでしょうか?
仮想関数にはしますが
C言語を使うと自分の足を誤って撃ち抜いてしまうことがある。 C++を使えばそのような間違いを犯しにくくなる。しかし、やってしまったときには足全体が無くなる。

笹山一郎吉岡
記事: 7
登録日時: 10年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#3

投稿記事 by 笹山一郎吉岡 » 10年前

お答えいただきありがとうございます。そうですね、ClassA,ClassB や他のいくつかの Move() が共通で、
他のクラスの Move() がそれぞれ個別の実装を持つときはいけますね。

もし

ClassA~C に共通な Move() { !!! }
ClassD~F に共通な Move() { ??? }
ClassG~I に共通な Move() { $$$ }
があって、ClassJ~Z はそれぞれ個別の Move() の実装を持つ場合、

ClassBase で “virtual void Move() { !!! }” と実装し、ClassA~C は Move() を再定義しないことで同じにできるのですが、
ClassD~F に共通な Move() と、ClassG~I に共通な Move() の実装をどうすればいいでしょうか。


目的は例えてみると、
基本クラス“動体”のもつメンバ変数“座標”を変化させる関数“動作”が、“動体”の派生クラス

“矢”、“弾丸”、“砲弾”について同じ実装 { 斜方投射処理 } を与え
“車”、“バイク”、“馬”について同じ実装 { 等速運動処理 } を与え
“鉄球”、“植木鉢”、“建材”について同じ実装 { 落下運動処理 } を与え
“羽”や“流水”その他については個別の実装 { 浮遊処理 } { 流動処理 } などを与える

とき、それぞれ同じ実装をコピペでない方法で行うには、以下の優先順位

1.コードの見やすさ
2.記述の短さ
3.必要なメモリの多さ
4.実行速度

で考えたときに、どうするのが最善なのかということです。
考え方としては、座標のメンバ変数を持つ基本クラスから派生したクラスに、
多重継承を行わずにその座標を変化させる処理を追加したいということです。
でも多重継承が一番良い方法であったら、多重継承を使いたいと思っています。


説明が上手くなくてすみません。でも読みやすくなるよう善処するのでお願いいたします。

zeek

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#4

投稿記事 by zeek » 10年前

私だったらこんな感じにするかな。

コード:

// ClassBase.hpp
class ClassBase
{
protected:
    int x, vel, acc;
public:
    typedef enum { NORMAL, TYPE_AB, TYPE_FZ } TYPE;
    ClassBase(TYPE type = NORMAL) : type_(type) {}
    virtual void Move() = 0;
private:
    TYPE type_;
};

// ClassBase.cpp
#include "ClassBase.hpp"

void ClassBase::Move()
{
    switch (type_) {
    case TYPE_AB:
        vel += acc;
        x += vel;
        break;
    case TYPE_FZ:
        ...
        break;
    }
}

// ClassA.hpp
#include "ClassBase.hpp"

class ClassA : public ClassBase
{
public:
    ClassA() : ClassBase(ClassBase::TYPE_AB) {}
    void FuncA()
    {
        // 実装
    }
    void Move()
    {
        ClassBase::Move();
    }
};
これであれば ClassBase を抽象クラスにしたまま派生クラスに共通のメソッドを持たせた上に保守性もそこそこかと思います。
なお、純粋仮想関数に関数定義を書いていますが、これは C++ 規格準拠ですので、念のため。

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

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#5

投稿記事 by beatle » 10年前

継承を用いるとどれか1つの実装しか継承できませんので、目的のことを達成するのは難しいですね。

以下のようにしたらどうでしょうか。
Movement1, Movement2, Movement3のようなクラスを作って、ClassA-CはMovement1を、ClassD-FはMovement2を、ClassG-IはMovement3を持つようにします。
ClassA-CのMoveメソッドはMovement1のMoveメソッドに処理を委譲します。

コード:

class Movement1 {
    int x_, vel_, acc_;
public:
    void Move() { !!! }
    int x() const { return x_; }
    int vel() const { return vel_; }
    int acc() const { return acc_; }
};
class ClassA {
    Movement1 movement_;
public:
    void Move() { movement_.Move(); }
};
さらなる改造として、Movement1,2,3クラスの共通インターフェースMovementでも作り、ClassA-IはMovementへのポインタ(スマートポインタがおすすめ)を持つようにしてもいいですね。更にコードの柔軟性が高まるかなあと思います。デザインパターンの「ステートパターン」に該当します。

zeek

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#6

投稿記事 by zeek » 10年前

No: 4 のコードは、派生クラスに依存する実装を基本クラスに実装しているからセンス悪すぎました。
無視してください。m(__)m

笹山一郎吉岡
記事: 7
登録日時: 10年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#7

投稿記事 by 笹山一郎吉岡 » 10年前

zeek様

ありがとうございます! 動作タイプを保存しておくというのは、シンプルで分かりやすいので、やはり見やすさという点では良いですよね。
変更も動作タイプを新たに追加するのと、switch文に新たな処理を書くだけなので、容易であると思われます。

単純にするため省いたのですが、それぞれのクラスは他にも様々なメンバ変数と関数を持っていて、
たとえば energy というメンバ変数に対する Suplly() という処理があり、Move() と Suplly() とクラスの対応を見ると

クラス( Move タイプ, Suplly タイプ )
ClassA( Move1, Suplly1 )
ClassB( Move1, Suplly2 )
ClassC( Move2, Suplly1 )
ClassD( Move2, Suplly3 )

となっていた場合、Supply() のタイプに対応するには、Suplly() のタイプを zeek 様のコードのように定義して、
switch 文のところで実装していけば上手くいきそうです。継承で考えると、Move() と Suplly() をそれぞれ別々に指定する場合は、
どうしても多重継承を使わなくてはならなそうです。ただ、switch 文での実装は処理の行数が長い場合、若干見辛くなりそうですね。
コードまで提示していただき感謝の極みです。


beatle 様

どうもありがとうございます! なるほど、これだとまさに、クラスに Move() を追加していく感じになりますね。
Strategy パターンを調べたときに、メンバ変数を渡して処理をしてもらっていたので、その部分が冗長な感じがしていましたが、
頂いたコードを拝見すると、Movement クラスに x, vel, acc のメンバを持たせ、適宜に取り出して使うということであっていますでしょうか。
これだとすっきりして変更がしやすくなっていますね。

もし Class に衝突判定の関数 Collision() と、現在座標からのエリア番号を取得する AreaNum() を追加することになったら、
Collision( Movement1.x() ), AreaNum( Movement1.x() )のように引数を渡すという風にすればいいのでしょうか。
Class に x, vel, acc を持たせたいと思っていましたが、むしろクラスの概念からいえば、“座標”と“移動処理”をまとめた“動作クラス”、
“燃料などの物資”と“補給・消費処理”をまとめた“物資クラス”などがあるべきかもしれません。ステートパターンについては調べてみます。
非常に参考になりました。


語彙が少ないのでうまく言えないですが、お二人とも本当にありがとうございました。

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

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#8

投稿記事 by beatle » 10年前

ちょっと長くなってしまいましたが、こんな感じでしょうか。
Movementクラス、Supplyクラスを動体オブジェクトの外に出せば、動体オブジェクトを継承した“矢”、“弾丸”、“砲弾”などのクラスは作らなくていいかもしれません。
(それらの差は動体オブジェクトのコンストラクタ引数として指定したMovement1,Movement2などの違いで表されます)

zeekさんのswitch文による解決法もシンプルな良い方法だと思います。
ご自身でお選びください。

コード:

struct MotionStatus {
    int x, vel, acc;
    MotionStatus()
        : x(0), vel(0), acc(0)
    {}
};

class Movement {
public:
    virtual ~Movement() {}
    virtual void Move() = 0;
    virtual MotionStatus CurrentMotionStatus() = 0;
};

class Movement1 : public Movement {
    MotionStatus ms_;
public:
    void Move() { !!! }
    MotionStatus CurrentMotionStatus() { return ms_; }
};

class MotionObject { // 動体
    std::shared_ptr<Movement> movement_;
public:
    MotionObject(const std::shared_ptr<Movement>& movement)
        : movement_(movement)
    {}
    void Move() { movement_->Move(); }
    bool Collides(const MotionObject& other) {
        // MotionObjectに外形を保持するメンバ変数を追加する?
        // movement_->CurrentMotionStatus()と
        // other.movement_->CurrentMotionStatus()を使って衝突判定
        return 衝突してるならtrue;
    }
};

// 利用側
MotionObject mo1(std::make_shared<Movement1>());
MotionObject mo2(std::make_shared<Movement2>());
mo1.Move();
if (mo1.Collides(mo2)) {
    // 衝突
}

笹山一郎吉岡
記事: 7
登録日時: 10年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#9

投稿記事 by 笹山一郎吉岡 » 10年前

beatle様

返事が遅くなってすみません。そして、何度もどうもありがとうございます。
継承ではなく外に出しておくのがいいというのは、とても同意します。
自分でも4つほど実装を書いてみたのですが、やはり継承というのはクラスの派生に使うものなので、
“関数を追加する”ように使うのは間違っていることがわかりました。

オブジェクト指向に慣れていると当然のことなのだと思われますが、
自分はどうもそのあたりが上手く理解できないようです。

様々な状況で、色々な組み合わせで必要になるメンバ変数や構造体について、
クラスにたくさんメンバ変数や構造体を持たせておいて、そのクラスにメソッドを次々追加し、
メンバ変数や構造体の操作を実装するというのは、ただのC言語+クラス構造ですね。

自分が調べた限りでは、c++でのオブジェクト指向の本当に良い方法というのは、
たくさんあるメンバ及びそれを操作するメソッドを、上手く切り分けて小さなクラスに分割し、
必要な時に Setter や Getter でメンバを設定・取得することであるというような印象を受けました。

やはりまだ圧倒的に経験が足りな過ぎたようなので、これからも試行錯誤して考えてみます。



例えに出したものとだいぶ違うのですが、色々試した結果のコードを投稿させていただきます
この掲示板の連投規制や文字数制限がよくわからなかったので、時間を開けて投稿します。
ご迷惑をおかけしたらすみません。

動体クラス MovingBody には、安定状態チェックの CheckStability() 動作の Move() 現在地取得の GetPos()
の関数が実装されています。それらの関数は MovingBody の様々な場所で参照される構造体を操作します(という設定です)。

以下が MovingBody を使用するソースコードです。
4つの MovingBody のインスタンスを作り、時間が経過する毎に安定状態チェックを行い、移動させ、座標を表示します。

コード:

// main.cpp
#include "implement_1.h"
#include <vector>

static const int MAX_TIME = 20;

int main()
{
    std::vector<MovingBody*> Movings;
    Movings.push_back(new Car);
    Movings.push_back(new Motorcycle);
    Movings.push_back(new Skateboard);
    Movings.push_back(new Bike);

    std::vector<MovingBody*>::iterator it;

    for(int time=0; time<MAX_TIME; time++)
    {
        for (it = Movings.begin(); it != Movings.end(); it++)
        {
            (*it)->CheckStability();
            (*it)->Move();
            (*it)->GetPos();
        }
        std::cout << std::endl;
    }

    char c;
    std::cin >> c;

    return 0;
}
以下が MovingBody が持つ構造体です

コード:

// params.h
// MovingObjectの持つ情報

struct KineticParameters
{
    int x, vel;
    KineticParameters() : x(0), vel(0) {};
};

struct GeneralStatus
{
    bool isStable, isControllable;
    GeneralStatus() : isStable(false), isControllable(false) {};
};
以下が多重継承による MovingBody の実装です。

コード:

// implement_1.h
// 多重継承による実装
// 失敗:ダイアモンド継承で個別に純粋仮想関数を実装できない
//    動作はするが、2つ以上の同じ名前を持つ関数があると警告が出る

#include <iostream>
#include <string>

#include "params.h"



// 動体基底クラス
class MovingBody
{
protected:
    KineticParameters KinPrms;
    GeneralStatus Status;
public:
    void GetPos(){ std::cout << KinPrms.x << '\t'; };
    virtual void Move() = 0;
    virtual void CheckStability() = 0;
};



// Move() 関数追加クラス 1
class EngineDriveBody : virtual public MovingBody
{
public:
    // 共通の移動関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void Move()
    {
        KinPrms.x += ++KinPrms.vel;
    }
    // ――――――――――――――――――――――――――――――――
};

// Move() 関数追加クラス 2
class LegMuscleDriveBody : virtual public MovingBody
{
public:
    // 共通の移動関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void Move()
    {
        if(KinPrms.vel <= 0) KinPrms.vel = 20;
        KinPrms.x += --KinPrms.vel;
    }
    // ――――――――――――――――――――――――――――――――
};



// CheckStability() 関数追加クラス 1
class TwoWheeledBody : virtual public MovingBody
{
public:
    // 共通の安定チェック関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void CheckStability()
    {
        Status.isStable = KinPrms.vel > 5; 
        Status.isControllable = KinPrms.vel < 15; 

        std::string msg = (Status.isStable) ? "s___" : "us__" ; 
        msg += (Status.isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ;
    }
    // ――――――――――――――――――――――――――――――――
};

// CheckStability() 関数追加クラス 2
class FourWheeledBody : virtual public MovingBody
{
public:
    // 共通の安定チェック関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void CheckStability()
    {
        Status.isStable = KinPrms.vel < 1000; 
        Status.isControllable = KinPrms.vel < 1500; 

        std::string msg = (Status.isStable) ? "s___" : "us__" ; 
        msg += (Status.isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ; 
    }
    // ――――――――――――――――――――――――――――――――
};



// 共通の関数を持つクラス 1-1
class Car : public EngineDriveBody, public FourWheeledBody
{
};

// 共通の関数を持つクラス 1-2
class Motorcycle : public EngineDriveBody, public TwoWheeledBody
{
};

// 共通の関数を持つクラス 2-1
class Skateboard :  public LegMuscleDriveBody, public FourWheeledBody
{
};

// 共通の関数を持つクラス 2-2
class Bike :  public LegMuscleDriveBody, public TwoWheeledBody
{
};
失敗です。基底クラスで純粋仮想関数 Move() を宣言し、片方の継承 (Engine または LegMuscle) で実装しても、
もう片方の (TwoWheeled, FourWheeled)にもMove() が継承されているので、
warning C4250 “2 つ以上のメンバーが同じ名前を持っています。”の警告が出ます。
かといって MovingBody に Move() などの関数を定義していないと、

MovingBody* obj = new Car;
obj->Move()

のように関数を使うことができません。ほぼ多重継承を使ったことがなかったので誤解していました。

以下は Strategy パターンによる実装です。

コード:

// implement_2.h
// Strategy パターンによる実装

#include<iostream>
#include<string>

#include "params.h"



// 移動方策
class MoveStrategy
{
public:
    virtual void Move(KineticParameters* KinPrms) = 0;
};

// 移動方策クラス 1
class ByEngine : public MoveStrategy
{
public:
    // 共通の移動関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void Move(KineticParameters* KinPrms)
    {
        KinPrms->x += ++KinPrms->vel;
    }
    // ――――――――――――――――――――――――――――――――
};

// 移動方策クラス 2
class ByLegMuscle : public MoveStrategy
{
public:
    // 共通の移動関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void Move(KineticParameters* KinPrms)
    {
        if(KinPrms->vel <= 0) KinPrms->vel = 20;
        KinPrms->x += --KinPrms->vel;
    }
    // ――――――――――――――――――――――――――――――――
};



// 安定化方策
class StabilizeStrategy
{
public:
    virtual void CheckStability(GeneralStatus* Status, int vel) = 0;
};

// 安定化方策クラス 1
class EquipTwoWheel : virtual public StabilizeStrategy
{
public:
    // 共通の安定チェック関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void CheckStability(GeneralStatus* Status, int vel)
    {
        Status->isStable = vel > 5; 
        Status->isControllable = vel < 15; 

        std::string msg = (Status->isStable) ? "s___" : "us__" ; 
        msg += (Status->isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ;
    }
    // ――――――――――――――――――――――――――――――――
};

// 安定化方策クラス 2
class EquipFourWheel : virtual public StabilizeStrategy
{
public:
    // 共通の安定チェック関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void CheckStability(GeneralStatus* Status, int vel)
    {
        Status->isStable = vel < 1000; 
        Status->isControllable = vel < 1500; 

        std::string msg = (Status->isStable) ? "s___" : "us__" ; 
        msg += (Status->isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ; 
    }
    // ――――――――――――――――――――――――――――――――
};



// 動体基底クラス
class MovingBody
{
protected:
    KineticParameters KinPrms;
    GeneralStatus Status;

    MoveStrategy* MyMoveStrategy;
    StabilizeStrategy* MyStabilizeStrategy;

    MovingBody( MoveStrategy* MovSt, StabilizeStrategy* StablSt ) :
        MyMoveStrategy(MovSt), MyStabilizeStrategy(StablSt) {};

public:
    void Move(){ MyMoveStrategy->Move(&KinPrms);  };
    void CheckStability(){ MyStabilizeStrategy->CheckStability(&Status, KinPrms.vel);  };

    void GetPos(){ std::cout << KinPrms.x << '\t'; };
};



// 共通の関数を持つクラス 1-1
class Car : public MovingBody
{
public:
    Car() : MovingBody( new ByEngine(), new EquipFourWheel() ) {}
};

// 共通の関数を持つクラス 1-2
class Motorcycle : public MovingBody
{
public:
    Motorcycle() : MovingBody( new ByEngine(), new EquipTwoWheel() ) {}
};

// 共通の関数を持つクラス 2-1
class Skateboard : public MovingBody
{
public:
    Skateboard() : MovingBody( new ByLegMuscle(), new EquipFourWheel() ) {}
};

// 共通の関数を持つクラス 2-2
class Bike : public MovingBody
{
public:
    Bike() : MovingBody( new ByLegMuscle(), new EquipTwoWheel() ) {}
};
基底クラスが非常に重たい見た目です。関数は処理に必要な変数や構造体を引数にしています。
この後作った包含の実装でも関数の実装の部分は大体同じです。

続きます。

笹山一郎吉岡
記事: 7
登録日時: 10年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#10

投稿記事 by 笹山一郎吉岡 » 10年前

以下が関数オブジェクトによる実装です。

コード:

// implement_3.h
// 内部クラスと関数オブジェクトによる実装

#include<iostream>
#include<string>

#include "params.h"



// 動体基底クラス
class MovingBody
{
protected:

    KineticParameters KinPrms;
    GeneralStatus Status;



    // 共通の安定チェック関数オブジェクトの基底
    class CheckStabilityFunction
    {
    public:
        virtual void operator()(MovingBody* Body) = 0;
    };

    // 共通の安定チェック関数の実装 1
    // ――――――――――――――――――――――――――――――――
    class CheckStWith2W : public  CheckStabilityFunction
    {
    public:
        void operator()(MovingBody* This)
        {
            This->Status.isStable = This->KinPrms.vel > 5; 
            This->Status.isControllable = This->KinPrms.vel < 15; 

            std::string msg = (This->Status.isStable) ? "s___" : "us__" ; 
            msg += (This->Status.isControllable) ? "c___" : "uc__" ; 
            std::cout << msg ;
        }
    };
    // ――――――――――――――――――――――――――――――――
    
    // 共通の安定チェック関数の実装 2
    // ――――――――――――――――――――――――――――――――
    class CheckStWith4W : public  CheckStabilityFunction
    {
    public:
        void operator()(MovingBody* This)
        {
            This->Status.isStable = This->KinPrms.vel < 1000; 
            This->Status.isControllable = This->KinPrms.vel < 1500; 

            std::string msg = (This->Status.isStable) ? "s___" : "us__" ; 
            msg += (This->Status.isControllable) ? "c___" : "uc__" ; 
            std::cout << msg ; 
        }
    };
    // ――――――――――――――――――――――――――――――――



    // 共通の移動関数オブジェクトの基底
    class MoveFunction
    {
    public:
        virtual void operator()(MovingBody* Body) = 0;
    };

    // 共通の移動関数の実装 1
    // ――――――――――――――――――――――――――――――――
    class MoveByEngine : public MoveFunction
    {
    public:
        void operator()(MovingBody* This)
        {
            This->KinPrms.x += ++This->KinPrms.vel;
        }
    };
    // ――――――――――――――――――――――――――――――――

    // 共通の移動関数の実装 2
    // ――――――――――――――――――――――――――――――――
    class MoveByLegMuscle : public MoveFunction
    {
    public:
        void operator()(MovingBody* This)
        {
            if(This->KinPrms.vel <= 0) This->KinPrms.vel = 20;
            This->KinPrms.x += --This->KinPrms.vel;
        }
    };
    // ――――――――――――――――――――――――――――――――



    MoveFunction* MoveFunc;
    CheckStabilityFunction* CheckStabilityFunc;

public:
    void GetPos(){ std::cout << KinPrms.x << '\t'; };

    virtual void Move() = 0;
    virtual void CheckStability() = 0;
};



// 共通の関数を持つクラス 1-1
class Car : public  MovingBody
{
public:
    void Move() { MoveByEngine()(this); }
    void CheckStability() { CheckStWith4W()(this); }
};

// 共通の関数を持つクラス 1-2
class Motorcycle : public  MovingBody
{
public:
    void Move() { MoveByEngine()(this); }
    void CheckStability() { CheckStWith2W()(this); }
};

// 共通の関数を持つクラス 2-1
class Skateboard : public  MovingBody
{
public:
    void Move() { MoveByLegMuscle()(this); }
    void CheckStability() { CheckStWith4W()(this); }
};

// 共通の関数を持つクラス 2-2
class Bike : public  MovingBody
{
public:
    void Move() { MoveByLegMuscle()(this); }
    void CheckStability() { CheckStWith2W()(this); }
};
自分で見いてもかなり見辛いです。内部クラスで関数オブジェクトを実装しているので、
this を引数にすることでクラスのメンバに自由にアクセスできます。
様々なメンバが必要になる場合、関数の引数にそれを指定すると変更やバグの発見が大変なので、
関数内から自由にアクセスしたいのですが、それはカプセル化に反していますね。
上でも書きましたが、自分はクラスの適切な使い方がわかっていないですね。

以下が包含を使った実装になります。

コード:

// implement_4.h
// 包含による実装

#include<iostream>
#include<string>

#include "params.h"




// エンジン
class Engine
{
public:
    // 共通の移動関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void Move(KineticParameters* KinPrms)
    {
        KinPrms->x += ++KinPrms->vel;
    }
    // ――――――――――――――――――――――――――――――――
};

// 脚
class LegMuscle
{
public:
    // 共通の移動関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void Move(KineticParameters* KinPrms)
    {
        if(KinPrms->vel <= 0) KinPrms->vel = 20;
        KinPrms->x += --KinPrms->vel;
    }
    // ――――――――――――――――――――――――――――――――
};



// 2つの車輪
class TwoWheel
{
public:
    // 共通の安定チェック関数の実装 1
    // ――――――――――――――――――――――――――――――――
    void CheckStability(GeneralStatus* Status, int vel)
    {
        Status->isStable = vel > 5; 
        Status->isControllable = vel < 15; 

        std::string msg = (Status->isStable) ? "s___" : "us__" ; 
        msg += (Status->isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ;
    }
    // ――――――――――――――――――――――――――――――――
};

// 4つの車輪
class FourWheel
{
public:
    // 共通の安定チェック関数の実装 2
    // ――――――――――――――――――――――――――――――――
    void CheckStability(GeneralStatus* Status, int vel)
    {
        Status->isStable = vel < 1000; 
        Status->isControllable = vel < 1500; 

        std::string msg = (Status->isStable) ? "s___" : "us__" ; 
        msg += (Status->isControllable) ? "c___" : "uc__" ; 
        std::cout << msg ; 
    }
    // ――――――――――――――――――――――――――――――――
};



// 動体基底クラス
class MovingBody
{
protected:
    KineticParameters KinPrms;
    GeneralStatus Status;
    
public:
    virtual void Move() = 0;
    virtual void CheckStability() = 0;

    void GetPos(){ std::cout << KinPrms.x << '\t'; };
};



// 共通の関数を持つクラス 1-1
class Car : public MovingBody
{
private:
    Engine InclPower;
    FourWheel InclWheel;
public:
    void Move(){ InclPower.Move(&KinPrms);; }
    void CheckStability(){ InclWheel.CheckStability(&Status, KinPrms.vel); }
};

// 共通の関数を持つクラス 1-2
class Motorcycle : public MovingBody
{
private:
    Engine InclPower;
    TwoWheel InclWheel;
public:
    void Move(){ InclPower.Move(&KinPrms);; }
    void CheckStability(){ InclWheel.CheckStability(&Status, KinPrms.vel); }
};

// 共通の関数を持つクラス 2-1
class Skateboard : public MovingBody
{
private:
    LegMuscle InclPower;
    FourWheel InclWheel;
public:
    void Move(){ InclPower.Move(&KinPrms);; }
    void CheckStability(){ InclWheel.CheckStability(&Status, KinPrms.vel); }
};

// 共通の関数を持つクラス 2-2
class Bike : public MovingBody
{
private:
    LegMuscle InclPower;
    TwoWheel InclWheel;
public:
    void Move(){ InclPower.Move(&KinPrms);; }
    void CheckStability(){ InclWheel.CheckStability(&Status, KinPrms.vel); }
};

見た目はすっきりしている気がします。ただ、やっていることは“関数を追加する”というより、
“クラスを追加する”という感じですね。

でも、そもそも

“たくさんのメンバを持つ原型のクラスに、そのメンバを操作する関数を追加することによって、目的のクラスを作りたい”

という発想が間違っていそうなので、これがいいのかもしれません。
この包含しているクラスにメンバを持たせれば、 beatle 様のコードになりますね。(なりますかね?)



もう一度、自分が何をしたかったのか書きますと、

元の状態:
  基本クラス:たくさんのメンバ、そのメンバに対するたくさんのメソッド



目指す状態:
  基本クラス:たくさんのメンバ
  +操作クラス:基本クラスのたくさんのメンバを操作するメソッド

にしたかったということですね。自分で書いていても不安になってきますが。
ですが、オブジェクト指向で考えれば、

理想的な状態:
  基本クラス:以下のクラスのインスタンス
    動作クラス:座標などのメンバ、それらのメンバを操作する移動などのメソッド
    判定クラス:燃料などのメンバ、それらのメンバが空でないか凍結していないかなど判定するメソッド
    状態クラス:電力・熱などのメンバ、それらのメンバの増減や、それが及ぼす影響を操作するメソド
    ・
    ・
    ・

のような感じになるのでしょうか。

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

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#11

投稿記事 by beatle » 10年前

その時々に一番合う方法を探せばよく、もしかしたら
目指す状態:
  基本クラス:たくさんのメンバ
  +操作クラス:基本クラスのたくさんのメンバを操作するメソッド
にするのが良い設計になる場合があるかもしれません。

が、一般論で言うなら、これは良くない設計だと思います。
基本的にクラスは、それ単体で完結したものにします。
親クラスのメンバは子クラスからでさえ触らない方が良いのです(一般論を言ってます)
なぜかというと、他人にメンバを直接いじられるとクラス全体としての整合性を保てなくなるかもしれないからというのが一つの理由。子クラスが親クラスにアクセスするときもメソッド経由にします。

クラスとしてまとめるのは「それ単体で動作する賢いデータ」を作るということです。
クラスは、メソッドを介して操作される限り整合性を保つのが理想です(往々にして理想と離れた設計になりますけど。)
笹山一郎吉岡さんの当初の目標のように、自分ではアクセスしないフィールドを持ち、それらを子クラスで操作する前提ですと、子クラスは親クラスの内部実装まで詳しく知っていなければなりません。
これでは、クラスとして分離した価値が大幅になくなってしまうでしょう。
クラスは、フィールドを内部に隠し(カプセル化)、公開されたインターフェースのみでアクセスするのが良いです。

笹山一郎吉岡
記事: 7
登録日時: 10年前

Re: 複数のクラスに共通な関数を定義する方法を知りたいです

#12

投稿記事 by 笹山一郎吉岡 » 10年前

beatle 様

また返信が遅くなってしまい、申し訳ありません。
いろいろとありがとうございました! 大変参考になりました。修業を積んできます。


閉鎖

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