Effective C++学習ノート(五)

7755 ワード

条項32:public継承成形is-a関係を確定する
(1)public継承はis-aを意味し、base classに適用されるすべてのことはderived classesにも適用されるに違いない.各devided classオブジェクトもbase classオブジェクトであるが、devided classオブジェクトに適用されるすべてのことはbase classに適用されるとは限らない.
条項33:継承された名前を隠すことを避ける
(1)derived class内の名前は、ローカルオブジェクトがグローバルオブジェクトを隠すようにbase classes内の名前を隠す
#include<iostream>
using namespace std;

class Base
{
private:
    int x;
public:
    virtual void mf1() = 0;
    virtual void mf1(int a)
    {
        cout << "Base::mf1(int a):"<< a << endl;
    }
    virtual void mf2()
    {
        cout <<"Base::mf2:" << endl;
    }
    void mf3()
    {
        cout << "Base::mf3:" << endl;
    }
    void mf3(double a)
    {
        cout << "Base::mf3(double)" << a << endl;
    }
};

class Derived:public Base
{
public:
    using Base::mf1;  // base class   mf1 mf3      derived      
    using Base::mf3;
    virtual void mf1()
    {
        cout << "Derived::mf1:"<< endl;
    }
    void mf3()
    {
        cout << "Derived::mf3:"<<endl;
    }
    void mf4()
    {
        cout << "Derived::mf4" << endl;
    }
};


void main()
{
    Derived d;
    int x = 10 ;

    d.mf1();
    d.mf1(x);
    d.mf2();
    d.mf3();
    d.mf3(x);
}

derivedにusing Base::mf 1とusing Base::mf 3を付けないと、derivedのmf 1関数はBaseクラスのmf 1関数を隠し、d.mf 1(x)とd.mf 3(x)を呼び出すとコンパイルエラーが発生し、このような関数が見つからないと言います.これはderivedのmf 1などの関数がbaseクラスのこのような関数を隠しているからです.
条項34:インタフェースの継承と継承の区別
(1)classの設計者として、derived classがメンバー関数のインタフェース(すなわち宣言)だけを継承することを望んでいる場合があります.derived classesが関数のインタフェースと実装を同時に継承することを望んでいる場合がありますが、上書きすることを望んでいます.(override)彼らが継承した実装.derived classが関数のインタフェースと実装を同時に継承し、何も上書きすることを許さないことがある.これらの機能を実現するためにpure virtualでインタフェースのみを継承することができ、impure virtualは継承インタフェースと実装を実現し、上書きすることができ、一般的な関数は継承インタフェースと実装を実現することができるが、上書きすることはできない.
(2)impure virtualは継承インタフェースと実装を実現し上書きできるため,サブクラスがデフォルトで継承されたimpure virtualを使用したくない場合,どのように解決すればよいのでしょうか.
#include<iostream>
using namespace std;

class Base
{
public:
virtual void print() =0; //           
};
void Base::print()//              ,         
{
    cout <<"Base::print()" << endl;
}

class Derived:public Base
{
public:
    
 void print()
 {
     Base::print();
     //cout << "Derived::print()" << endl;
 }
};

void main()
{
   Base *pBase = new Derived;
   pBase->print();
}
この解決策を使用して、virtual関数にデフォルトの実装を提供することができます.サブクラスはvirtual関数を実装する必要があるため、サブクラスは親が実装したメソッドをデフォルトで継承することはありません.サブクラスは親が提供するデフォルトの実装メソッドを呼び出すことができますが、このメソッドを呼び出すにはサブクラスの明示的な呼び出しが必要です.これにより、サブクラスがデフォルトで親クラスのimpure virtual関数を継承する問題を回避できます.
条項25:virtual関数以外の選択を考慮する
(1)Non-Virtual Interface手法によるTemplate Methodモードの実現
#include<iostream>
using namespace std;

class GameCharacter
{
public:
    int healthValue() const
    {
        ..... //       
        int retVal = doHealthValue();
        ..... //       
    }
private:
    virtual int doHealthValue() const
    {
        .....
    }
};
このクラスでhealthValue関数ではdoHealthValue()という虚関数を呼び出し、この虚関数を呼び出す前にいくつかの初期化作業を行い、この関数を呼び出した後にいくつかの初期化作業を行い、これらはスケルトンと見なすことができ、doHealthValue()という虚関数はサブクラスで拡張を遅らせることができ、これが設計モードのtemplateモードである.
(2)Funtion PointersによるStrategyモードの実現
この方法は,このクラスに別のクラスのポインタまたはオブジェクトを保存し,保存したポインタまたはオブジェクトがそのオブジェクトを呼び出す方法によりStrategyモードを実現することを考えている.クラス内のvirtual関数をポインタメンバー変数に置き換えることで、virtualよりも柔軟になります.より多くのクラスを構築する必要はありません.
条項36:継承されたnon-virtual関数を再定義しない
(1)継承されたnon-virtual関数を再定義するとマスク現象(サブクラスのメソッドのような親のメソッド)が発生し,non-virtual関数は不変性を表し,サブクラスでこの関数を再定義する場合は,この関数がサブクラスで変更されるべきであることを示し,non-virtual関数ではなくvirtual関数であるべきである.
#include<iostream>
using namespace std;
class B
{
public:
    void mf()
    {
        cout << "B::mf" << endl;
    }
};
class D:public B
{
public:
    void mf()
    {
        cout << "D::mf" << endl;
    }
};
void main()
{
    D x;
    B *pB = &x;
    pB->mf();
    D *pD = &x;
    pD->mf();
}
これは、ポインタのタイプが呼び出した関数が誰の関数であるかを決定する問題のためであり、virtual関数にはこのような問題は存在しない.継承されたnon-virtual関数は絶対に再定義しないでください.
条項37:継承されたデフォルトのパラメータ値を再定義しない
(1)virtual関数は動的にバインドされ、デフォルトパラメータは静的にバインドされているため、関数はサブクラスのvirtual関数を呼び出しますが、パラメータは親のデフォルトパラメータであり、サブクラスにもパラメータのデフォルトパラメータが定義されていても、親の省電力パラメータが使用されます.
#include<iostream>
using namespace std;
class Shape
{
public:
    enum ShapeColor{ Red,Green,Blue};
    virtual void draw( ShapeColor color = Red ) const = 0;
};

void Shape::draw(ShapeColor color) const 
{
    cout << color << endl;
}
class Rectangle: public Shape
{
public:
    virtual void draw( ShapeColor color = Green ) const
    {
        cout << color << endl;
    }
};
class Circle :public Shape
{
public:
    virtual void draw( ShapeColor color) const
    {
        cout << color << endl;
    }
};
void main()
{
    Shape *ps1;
    ps1 = new Rectangle;
    ps1->draw();
    delete ps1;
    Shape *ps2;
    ps2 = new Circle;
    ps2->draw();
    delete ps2;
}

(2)virtual関数のデフォルトパラメータに遭遇した場合,設計の代わりに以下の方法を用いることができる.
#include<iostream>
using namespace std;
class Shape
{
public:
    enum ShapeColor{ Red,Green,Blue};
    void draw( ShapeColor color = Red ) const
    {
        doDraw( color );
    }
private:
    virtual void doDraw( ShapeColor color ) const = 0;
};

void Shape::doDraw(ShapeColor color) const 
{
    cout << color << endl;
}
class Rectangle: public Shape
{
public:
    virtual void doDraw( ShapeColor color ) const
    {
        cout << color << endl;
    }
};

non-virtual関数はderived classに絶対に上書きされないため、この設計はcolorのデフォルトパラメータ値を明確に使用している.
条項38:複合成形によるhas-aまたは「あるものによる」
(1)複合の意味はpublic継承とは全く異なる.public継承はis-a,複合の意味はhas-a,is-aはサブクラスが親クラス,has-aは1つのクラスに別のクラスがあるが,2つのクラスは互いに取って代わることができず,クラスAがクラスBを含む場合,クラスAはクラスBの関数を呼び出すことができる.
条項39:private継承を賢明かつ慎重に使用
(1)クラスDpublic継承クラスBであれば、is-aを表すので、クラスDは自動的にクラスDをクラスBに変換することができ、private継承であれば、is-aではなく、privateはクラスDとクラスBに必然的なつながりがないことを表すが、クラスDはクラスBのいくつかの方法や関数を使用したいだけで、クラスDとクラスBの間に必然的なつながりがないので、クラスDは自動的にクラスB.次の手順に従います.
#include<iostream>
using namespace std;
class B
{
public:
    int x;
    virtual void print()
    {
        cout << "B::print" << endl;
    }
};
class D: private B
{
public:
    virtual void print()
    {
        cout << "D::print" << endl;
    }
};

void callPrint( B &rb )
{
    rb.print();
}
void main()
{
   B b;
   D d;
   callPrint( b );
   callPrint( d );//    :error C2243: 'type cast' : conversion from 'class D *' to 'class B &' exists, but
                   //is inaccessible
}
(2)private と の は、いくつかのクラスを する です.では、 はどのように されますか.まず、 の を てみましょう.
#include<iostream>
using namespace std;
class Empty
{
};

class HoldAnInt
{
private:
    int x;
    Empty e;//          
};
はsizeof(HoldAnInt)>sizeof(int)を します.サイズが0の したオブジェクトは、c++ にchar(またはintなど)を のオブジェクトに するように じられているので、privateを うとスペースを することが です.
privateで すれば、 クラスが クラスのいくつかの を き えることができる 、 たちの はただ クラスにこれらの を させたいだけなのかもしれませんが、これは たちの に しますので、この は み わせを ったほうがいいです.
(3)C++はクラス クラスを することも
#include<iostream>
using namespace std;
class Empty
{
public:
    class test
    {
    public:
        void print()
        {
            cout << "Empty::test::print" << endl;
        }
    };
    test t;
};

void main()
{
    Empty e;
    e.t.print();
}
40: かつ な の
(1) は になりやすく,ダイヤモンド は, クラスが の クラスのメンバを つようになる.