『C++沈思録』読書ノート――エージェント類


Andrew KoenigとBarbara MooはC++研究分野の「第一の神仙家族」と呼ばれ、彼らの本を読むのは理性的だ.今回説明するのはC++のもう一つのよくある問題です.
異なるサブクラスオブジェクトをコンテナにバインドするために、メモリ割り当てを優美に制御する方法を見つけます.
どんなに複雑な一言でも、慌てないで、実は簡単で、歩みに従って見てください.
まず、一連の交通機関のクラスを設計すると仮定します.一般的には、すべての交通機関にあるメンバーと属性を格納する交通機関のベースクラスを定義します.
class Vehicle {
public:
    virtual double weight() const = 0;
    virtual void start() = 0;
    // ......
};

次に、次のような交通機関の継承関係があります.
class RoadVehicle : public Vehicle { /* ...... */ };
class AutoVehicle : public RoadVehicle { /* ...... */ };
class Aircraft    : public Vehicle { /* ...... */ };
class Helicopter  : public Aircraft { /* ...... */ };

次に、異なるタイプの交通手段を保存するためのコンテナを定義します.
この要求は簡単そうに見えますが、思ったほど簡単ではありません.例えば、配列を使って異なる交通手段を保存すると、まずそう書くかもしれません.
Vehicle parking_lot[1000];

よく考えてみると、このように書くのは間違っているようですが、どうしてですか.Vehicleには純粋な虚関数があるので、Vehicleは抽象クラスであり、抽象クラスにはオブジェクトがないので、このように定義するのは絶対にだめです.一般的な分析はここまでですが、Vehicleのすべての純虚関数を削除すると、この定義はOKのようで、文法的には問題ありませんが、次のような賦値があります.
Helicopter x = /* ...... */
parking_lot[num_vehicles++] = x;

このような付与は、HelicopterオブジェクトをVehicleオブジェクトに変換し、自分のHelicopter属性を失うことになります.これは私たちが望んでいるものではありません.これはdouble数を整形配列に変換し、自分の小数部を失うようなものです.
ここを見て、すぐに誰かが提出します.それではparking_lotにVehicleのポインタを格納すればよいのではないでしょうか.一緒に見てみましょう
Vehicle *parking_lot[1000];    //     

次に、上記の割り当て操作を繰り返します.
Helicopter x = /* ...... */
parking_lot[num_vehicles++] = &x;

すべてOKのように見えますが、でも経験のあるプログラマーは(例えば私、:))一目でここが危険なのか、なぜ危険なのか、記憶ポインタ自体が危険なことなので、具体的には、ここのxは局所変数のように見えますが、xが解放されるとparking_lot配列のポインタがすぐに懸垂ポインタになり、何を指しているのか分かりません.責任感のある行程です序員は絶対にそんなことはしないだろう.
私たちは折れていないのではないでしょうか.いいえ、ポインタを置くことができない以上、このオブジェクトをコピーしてみましょう.以下のようにします.
Helicopter x = /* ...... */
parking_lot[num_vehicles++] = new Helicopter(x);

時間とメモリを無駄にしましたが、確かにそうすることができます.自分でメモリを割り当てたのはもちろん自分で解放するので、deleteというparking_を規定し続けます.lotのとき、私たちもその中で指向しているオブジェクトを解放します.自分でメモリを管理するだけの負担なら、まだ納得できると思いますが、ここにはそれほど明らかではない問題があります.パーキングを入れてlotの中のオブジェクトは、既知のタイプのオブジェクトでなければなりません.ここにいる見官といえばすぐに私の意味がわかります.つまり、コンパイル時にタイプが未知のオブジェクトについては、ここでは保存できません.例えば、parking_lot[p]中放parking_lot[q]の相手、どうすればいいですか?我々はパーキングを知らないlot[q]のオブジェクトタイプなので、このオブジェクトをコピーすることはできません.同時に、parking_lotには2つのポインタが同じオブジェクトを指しています.私たちはこのコンテナを削除するときに中のオブジェクトも削除します.2つのポインタが同じオブジェクトを指している場合は2回削除します.もちろん、他の方法で避けることができますが、これは私に耐えられません.
コンパイル時の未知のオブジェクトに対して、賢いプログラマーはすでに方法を考えて解決しました.なぜ私たちはそれらが何なのか知っていますか?自分たちが自分が何なのか知っていれば、教えてくれればOKですよ.good boy!はっきり言って、私たちはVehicleから継承されたクラスに何なのかを他の人に伝えることができます.簡単な方法は、Vehicleで定義されたcopyの純虚関数を継承し、Vehicleから継承されたクラスは、呼び出し者に自分のcopy関数をコピーするために自分のcopy関数を設計します.これにより、呼び出し者はこれらのめちゃくちゃな交通手段が何なのかを知る必要はありません.コードの変更を続行します.
class Vehicle {
public:
    virtual double weight() const = 0;
    virtual void start() = 0;
    virtual Vehicle *copy() const = 0;
    // ......
};

次に、Helicopterクラスを変更し、copy関数を追加します.
Vehicle *Helicopter::copy() const
{
    return new Helicopter(*this);
}

xのタイプやparkingを知る必要はありませんlot[q]のタイプは、x.copy()関数またはparking_を直接呼び出します.lot[q]->copy()関数でOKです.
parking_lot[num_vehicles++] = x.copy();
parking_lot[p] = parking_lot[q]->copy();

私たちは上記の2番目の問題を完璧に解決しましたが、プログラマーはこれまで完璧を追求してきました.では、この表示処理メモリの割り当ての問題を解決する方法はありますか?これもプログラマーの幸せな場所で、他の分野で完璧を追求するのは極めて難しいが、コードはいつも私たちを喜ばせる.「C++沈思録」には、「類で概念を表す」という非常に深い概念が言及されていますが、いったいどういう意味ですか.つまり、私たちのデザインクラスは、具体的なものだけでなく、概念でもあります.例えば、あなたはクラスで人、男、女などを表すことができます.同じように、クラスで家庭を表すことができます.人は具体的ですが、家庭はただの概念です.家庭には必ず人がいます.だから、家庭という概念をコントロールしました.私に家族がいない人がいると言わないで、例を挙げてください.親!).
具体的には、コード上でエージェントクラスを定義することによって、これらの異なる交通手段を表現することができます.このエージェントクラスは異なる交通手段を代表することができるはずです.同時に、メモリの管理を助ける必要があります.そして、インスタンス化する必要があります.そうすれば、私は上のVehicleが抽象クラスで容器を定義できないという問題にこだわる必要はありません.このエージェントクラスの役割は、メモリの管理問題を考慮する必要がなく、コンパイル時にタイプが不明な場合をサポートするエージェントクラスのコンテナを定義できるようにすることです.
エージェント類は交通機関を管理する管理者にすぎず、具体的なものではなく、大スターのマネージャーと同じです.それはスターを保存しなければならないようです.つまり、交通機関を指すポインタが必要です.同時に、ステージに上がる必要があります.では、実際の構造関数が必要です.同時に、容器に入れる必要があります.そのため、デフォルトの構造関数が必要です.
class VechicleProxy {
public:
    VechicleProxy();
    VechicleProxy(const Vehicle &);
    ~VechicleProxy();
    VechicleProxy(const VechicleProxy &);
    VechicleProxy &operator=(const VechicleProxy &);
private:
    Vehicle *p;
};

上にはいくつかのコンストラクション関数と付与オペレータが追加されていますが、理解しにくいわけではありません.結局は本当のクラスですね.その中でconst Vehicle&をパラメータとするレプリケーション構造関数は、任意の交通機関をエージェントする能力を提供します.すべてはOKに見えますが、デフォルトのコンストラクション関数ではpポインタに何を割り当てることができますか?0にしかならないようです.このゼロポインタは、つまり通常言う空のエージェントです.では、このエージェントクラスのメンバー関数を完了しましょう.
VechicleProxy::VechicleProxy(): p(0) { }
VechicleProxy::VechicleProxy(const Vehicle &BigStar): p(BigStar.copy()) {}
VechicleProxy::~VechicleProxy() { delete p; }
VechicleProxy::VechicleProxy(const VechicleProxy &v): p(v.p ? v.p->copy() : 0) {}
VechicleProxy::operator=(const VechicleProxy &v)
{
    if (this != &v)
    {
        delete p;
        p = (v.p ? v.p->copy() : 0);
    }
    return *this;
}

ここには余計な秘密はありませんから、細かくしてもOKです.ここまで書いてやっと完璧なparkingを定義することができますlotしました.
VehicleProxy parking_lot[1000];
Helicopter x;
parking_lot[num_vehicles++] = x;

まとめてみます.
継承とコンテナを使用する場合、通常、メモリの割り当てとコンパイル時にタイプ不明のオブジェクトのバインドという2つの問題を処理する必要があります.エージェントクラスとなるものを用いて,複雑な継承階層を圧縮し,このクラスがすべてのサブタイプを代表できるようにし,クラスで概念を表す武器はやはり鋭い.