[セットトップ]C++Primer学習ノート_64_フルロードオペレータと変換-変換とクラスタイプ【下】

7177 ワード

リロードオペレータと変換
--変換とクラスタイプ【下】
四、重荷決定とクラスの実パラメータ
変換関数の実パラメータが必要な場合、コンパイラはクラスの変換オペレータまたはコンストラクション関数を自動的に適用します.したがって、関数の決定中にクラス変換オペレータを考慮する必要があります.関数リロードの決定は、次の3つのステップで構成されます.
1)候補関数の集合を決定する:これらは呼び出された関数と同じ名前の関数である.
2)実行可能な関数を選択:これらは、関数呼び出しの実パラメータに一致するパラメータの数とタイプの候補関数です.実行可能な関数を選択すると、変換操作がある場合、コンパイラは各パラメータに一致する変換操作を決定します.
3)最適に一致する関数を選択します.最適マッチングを決定するために、実パラメータを対応するパラメータに変換するために必要なタイプ変換を分類します.クラスタイプの実パラメータとパラメータの場合、可能な変換のセットにはクラスタイプ変換が含まれます.
1、変換オペレータ後の標準変換
リロードセットの2つの関数が同じ変換関数で一致する場合、変換後または以前の標準変換シーケンスのレベルを使用して、どの関数が最適な一致を有するかを決定します.
そうでなければ、異なる変換操作を使用することができる場合、この2つの変換は、必要または不要な標準変換のレベルにかかわらず、同じように一致すると考えられます.
注意:クラスタイプ変換後の標準変換シーケンスを選択基準として使用するのは、2つの変換シーケンスが同じ変換操作を使用している場合のみです.
2、複数の変換と再ロードの決定
class SmallInt
{
public:
    operator int() const
    {
        return val;
    }

    operator double() const
    {
        return val;
    }
    //...

private:
    std::size_t val;
};

    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous

この例では、operatororintを使用してsiを変換し、intパラメータを受け入れるcomputeバージョンを呼び出すか、operatordoubleを使用してsiを変換してcompute(double)を呼び出すことができます.
コンパイラは、2つの異なるクラスタイプの変換を区別しようとしません.具体的には、1つの呼び出しがクラスタイプ変換の後に標準変換と完全に一致する必要がある場合でも、コンパイラは呼び出しをエラーとしてマークします.次のようになります.
    void compute(int);
    //void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous,      

3、明示的強制変換による二義性の排除
    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(static_cast<int>(si));  //OK
    compute(static_cast<double>(si));   //OK

4、標準変換と構造関数
class SmallInt
{
public:
    SmallInt(int = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);
    /*
    *:      Integral     int     manip       ,
    *      SmallInt     int     manip       。
    */
    manip(10);  //Error

クラスの1つが、標準変換を必要とするコンストラクション関数を定義している場合でも、この関数呼び出しには2つの意味がある可能性があります.たとえば、SmallIntがintパラメータではなくshortを受け入れる構造関数を定義している場合、関数呼び出しmanip(10)は、構造関数を使用する前にintからshortへの標準変換を必要とします.関数呼び出しのリロードバージョンで選択すると、1つの呼び出しには標準変換が必要であり、もう1つの呼び出しには必要ありません.この事実は実質的ではありません.コンパイラは直接関数を構築するのが好きではありません.呼び出しには2つの意味があります.次のようになります.
class SmallInt
{
public:
    // int  short
    SmallInt(short = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);

    //          
    manip(10);  //Error

5、明示的な構造関数呼び出しによる二義性の排除
    manip(SmallInt(10));    //OK
    manip(Integral(10));    //OK

【警告!】
リロード関数を呼び出す場合は、コンストラクション関数または強制タイプを使用して実パラメータを変換する必要があります.これは設計の拙劣な表現です.
//P463   14.44
class LongDouble
{
public:
    operator double () const
    {
        cout << "double!" << endl;
        return val;
    }
    operator float () const
    {
        cout << "float!" << endl;
        return val;
    }

private:
    double val;
};

int main()
{
    LongDouble ldObj;
    int ex1 = ldObj;    //Error
    float ex2 = ldObj;  //OK : float
}
//  14.45
class LongDouble
{
public:
    LongDouble(double);

private:
    double val;
};

void calc(int temp)
{
    cout << "int" << endl;
}
void calc(LongDouble temp)
{
    cout << "LongDouble" << endl;
}

int main()
{
    double dval;
    //    void calc(int):              
    calc(dval);
}

五、リロード、変換、オペレータ
リロードオペレータは、リロード関数です.リロード関数呼び出しを決定するのと同じ手順を使用して、指定した式にどのオペレータ(内蔵またはクラスタイプ)を適用するかを決定します.
    ClassX sc;
    int iobj = sc + 3;

4つの可能性があります.
1)重荷重の加算オペレータがClassXとintに一致する.
2)変換が存在し、scおよび/またはint値を+が定義されたタイプに変換する.この場合、式は変換を先に使用し、適切な加算オペレータを適用します.
3)変換オペレータと+のリロードバージョンが定義されているため、この式には二義性がある.
4)変換もリロードもない+が使用できるため、この式は不正です.
1.リロードの決定とオペレータ
メンバー関数と非メンバー関数が可能であり、この事実は候補関数セットを選択する方法を変えた!
オペレータのリロード決定は、一般的な3つの手順に従います(一般的には、-D):
1)候補関数の選択
2)各実パラメータの潜在的な変換シーケンスを識別することを含む実行可能な関数の選択
3)最適一致関数の選択
2、オペレータの候補関数
一般に、候補関数セットは、使用される関数と同名のすべての関数から構成され、使用される関数は、関数呼び出しから見ることができる.オペレータが式で使用される場合、候補関数には、オペレータの組み込みバージョンと、オペレータの通常の非メンバーバージョンが含まれます.また、左オペレータにクラスタイプがあり、クラスがオペレータのリロードバージョンを定義している場合、候補セットにはオペレータのリロードバージョンが含まれます.
一般に、関数呼び出しの候補セットには、メンバー関数または非メンバー関数のみが含まれ、両方は含まれません.オペレータの使用を決定すると、オペレータの非メンバーバージョンとメンバーバージョンが候補となる場合があります.
指定した関数の呼び出しを決定する場合、オペレータの使用とは逆に、呼び出し自体によって考慮した名前の役割ドメインが決定されます.クラスタイプのオブジェクト(またはそのオブジェクトの参照またはポインタ)から呼び出される場合は、クラスのメンバー関数のみを考慮します.同じ名前のメンバー関数と非メンバー関数は、互いに再ロードされません.リロードオペレータを使用する場合、呼び出し自体は使用するオペレータ関数の役割ドメインに関連することを教えてくれません.そのため、メンバーと非メンバーバージョンは考慮する必要があります.
【警告:変換とオペレータ】
クラスのリロードオペレータ、変換構造関数、変換関数を正しく設計するには、注意が必要です.特に、クラスが変換オペレータとリロードオペレータを定義している場合、二義性が生じやすい.次の経験則が役立ちます.
1)相互変換のクラスを定義しないでください.すなわち、クラスFooがクラスBarを受け入れるオブジェクトのコンストラクション関数を持っている場合、クラスBarのタイプFooへの変換オペレータを定義しないでください.
2)組み込み算術タイプへの変換を避ける.具体的には、演算タイプへの変換が定義されている場合、
a)算術タイプを受け入れるオペレータのリロードバージョンを定義しないでください.ユーザーがこれらのオペレータを使用する必要がある場合、変換オペレータは定義したタイプのオブジェクトを変換し、組み込みオペレータを使用できます.
b)1つ以上の算術タイプへの変換を定義しない.標準変換を他の算術タイプへの変換を提供します.
最も簡単なルールは、「明らかに正しい」場合は、変換関数の定義を避け、非明示的な構造関数を制限することです.
3、変換は内蔵オペレータの二義性を引き起こす可能性がある
class SmallInt
{
public:
    SmallInt(int = 0);
    operator int() const
    {
        return val;
    }

    friend SmallInt operator+(const SmallInt &,const SmallInt &);
private:
    std::size_t val;
};

int main()
{
    SmallInt s1,s2;

    //       SmallInt    +      
    SmallInt s3 = s1 + s2;  //OK

    //   0     SmallInt     +   SmallInt   
    //     s3     int      int          。
    int i = s3 + 0; //Error
}

【地雷に注意】
演算タイプに変換関数を提供したり、同じタイプにリロードオペレータを提供したりすると、リロードオペレータと内蔵オペレータの間の二義性が生じる可能性があります.
4、実行可能なオペレータと変換
各呼び出しに実行可能な関数をリストすることで、この2つの呼び出しの動作を理解できます.最初の呼び出しには、2つの実行可能な加算オペレータがあります.
    SmallInt operator+(const SmallInt &,const SmallInt &);
        operator+(int, int)

最初の加算は、実パラメータ変換を必要としません.s 1およびs 2は、パラメータのタイプと完全に一致します.組み込みオペレータを使用すると、2つの実パラメータを変換する必要があるため、リロードオペレータは2つの実パラメータとよく一致するため、呼び出されます.
2番目の加算:
    int i = s3 + 0; // error: ambiguous

2つの関数は同じように実行できます.この場合、リロードされた+バージョンは最初の実パラメータと完全に一致し、組み込みバージョンは2番目の実パラメータと完全に一致します.1番目の実行可能関数は左操作数に対して好ましく、2番目の実行可能関数は右操作数に対して好ましい.最適な実行可能関数が見つからないため、呼び出しは二義的にマークされます.
//P466   14.46             operator+
class Complex
{
public:
    Complex(double);
};

class LongDouble
{
    friend LongDouble operator+(LongDouble &,int);  //1

public:
    LongDouble(int);
    operator double ();
    LongDouble operator+(const Complex &);      //2
    //..
};
LongDouble operator+(const LongDouble &,double);    //3

int main()
{
    LongDouble ld(16.08);
    double res = ld + 15.05;    //  3,    ?
}