46

次の簡単な例を考えてみましょう。

template <class Type>
class smartref {
public:
    smartref() : data(new Type) { }
    operator Type&(){ return *data; }
private:
    Type* data;
};

class person {
public:
    void think() { std::cout << "I am thinking"; }
};

int main() {
    smartref<person> p;
    p.think(); // why does not the compiler try substituting Type&?
}

変換演算子は C++ でどのように機能しますか? (つまり) コンパイラはいつ変換演算子の後に定義された型を置換しようとしますか?

4

7 に答える 7

63

変換関数が使用される場合と使用されない場合のいくつかのランダムな状況は次のとおりです。

まず、変換関数は、同じクラス型または基本クラス型への変換には使用されないことに注意してください。

引数渡し時の変換

引数の受け渡し中の変換では、コピーの初期化の規則が使用されます。これらのルールは、参照に変換するかどうかに関係なく、変換関数のみを考慮します。

struct B { };
struct A {
  operator B() { return B(); }
};
void f(B);
int main() { f(A()); } // called!

引数の受け渡しは、コピーの初期化の 1 つのコンテキストにすぎません。もう 1 つは、コピー初期化構文を使用した「純粋な」形式です。

B b = A(); // called!

参照への変換

条件演算子では、変換後の型が左辺値の場合、参照型への変換が可能です。

struct B { };
struct A {
  operator B&() { static B b; return b; }
};

int main() { B b; 0 ? b : A(); } // called!

参照への別の変換は、参照を直接バインドする場合です。

struct B { };
struct A { 
  operator B&() { static B b; return b; }
};

B &b = A(); // called!

関数ポインタへの変換

関数ポインターまたは参照への変換関数があり、呼び出しが行われると、それが使用される場合があります。

typedef void (*fPtr)(int);

void foo(int a);
struct test {
  operator fPtr() { return foo; }
};

int main() {
  test t; t(10); // called!
}

このことは、実際には非常に役立つ場合があります。

非クラス型への変換

いつでもどこでも発生する暗黙の変換は、ユーザー定義の変換も使用できます。ブール値を返す変換関数を定義できます

struct test {
  operator bool() { return true; }
};

int main() {
  test t;
  if(t) { ... }
}

(この場合の bool への変換は、他の整数型への変換を禁止するために、safe-bool イディオムによってより安全にすることができます。) 変換は、組み込み演算子が特定の型を期待する場所ならどこでもトリガーされます。ただし、変換が邪魔になる場合があります。

struct test {
  void operator[](unsigned int) { }
  operator char *() { static char c; return &c; }
};

int main() {
  test t; t[0]; // ambiguous
}

// (t).operator[] (unsigned int) : member
// operator[](T *, std::ptrdiff_t) : built-in

メンバーの場合は 2 番目のパラメーターに変換が必要であり、組み込み演算子の場合は最初のパラメーターにユーザー定義の変換が必要なため、呼び出しがあいまいになる可能性があります。他の 2 つのパラメーターはそれぞれ完全に一致します。場合によっては、呼び出しがあいまいになることはありません (そのときとはptrdiff_t異なる必要がありますint)。

変換関数テンプレート

テンプレートにはいくつかの優れた機能がありますが、それらについては十分に注意してください。次の例では、型を任意のポインター型に変換できます (メンバー ポインターは "ポインター型" とは見なされません)。

struct test {
  template<typename T>
  operator T*() { return 0; }
};

void *pv = test();
bool *pb = test();
于 2009-08-20T19:31:18.663 に答える
17

「。」演算子は C++ ではオーバーロードできません。また、xy と言うたびに、x に対して自動的に変換が実行されることはありません。

于 2009-08-20T18:24:43.727 に答える
10

コンバージョンは魔法ではありません。A に B への変換があり、B に foo メソッドがあるからといって、a.foo() が B::foo() を呼び出すとは限りません。

コンパイラは、4 つの状況で変換を使用しようとします。

  1. 変数を別の型に明示的にキャストする
  2. その位置で異なる型を期待する関数に変数を引数として渡します(ここでは演算子は関数としてカウントされます)
  3. 変数を別の型の変数に割り当てます
  4. 変数 copy-construct を使用するか、異なる型の変数を初期化します

継承に関連する変換以外に、3 種類の変換があります。

  1. 組み込みの変換 (例: int から double)
  2. クラス B が型 A の 1 つの引数を取るコンストラクターを定義し、それを "explicit" キーワードでマークしない暗黙の構築。
  3. クラス A が演算子 B を定義するユーザー定義の変換演算子 (例のように)

コンパイラがどのタイプの変換をいつ使用するかを決定する方法 (特に複数の選択肢がある場合) はかなり複雑であり、それを SO の回答に要約しようとするのは悪い仕事です。C++ 標準のセクション 12.3では、暗黙的な構築とユーザー定義の変換演算子について説明しています。

(思いつかなかった変換のシチュエーションや方法があるかもしれませんので、抜けているところがあればコメントや編集お願いします)

于 2009-08-20T18:22:27.290 に答える
7

関数 (クラスのオーバーロードされた演算子と既定の演算子を含む) にパラメーターを渡すときに、暗黙的な変換 (変換演算子または非明示的なコンストラクターによるもの) が発生します。これに加えて、算術型に対して実行される暗黙の変換がいくつかあります (そのため、char と long を追加すると、2 つの long が追加され、結果は long になります)。

暗黙的な変換は、メンバー関数呼び出しが行われるオブジェクトには適用されません。暗黙的な変換のために、"this" は関数パラメーターではありません。

于 2009-08-20T18:19:58.137 に答える
1

T必要なタイプのオブジェクト (参照) を使用しようとすると、コンパイラは 1 つ (!) ユーザー定義のキャスト (暗黙の ctor またはキャスト演算子) を試みますU

.ただし、演​​算子は常に左側のオブジェクト (参照) のメンバーにアクセスしようとします。それが定義されている方法です。もっと派手なものが必要な場合は、それoperator->()がオーバーロードできるものです。

于 2009-08-20T18:19:04.473 に答える
0

やったほうがいい

((person)p).think();

コンパイラには、自動的に person にキャストするための情報がないため、明示的なキャストが必要です。

次のようなものを使用する場合

person pers = p;

次に、コンパイラは person への暗黙的なキャストに関する情報を取得します。

コンストラクターを介して「キャスト」できます。

class A
{
public:
   A( int );
};


A a = 10; // Looks like a cast from int to A

これらはいくつかの簡単な例です。キャスト (暗黙的、明示的など) については、さらに説明する必要があります。詳細については、本格的な C++ の書籍を参照してください (適切なタイトルについては、スタック オーバーフローに関する C++ の書籍に関する質問を参照してください)

于 2009-08-20T18:14:58.677 に答える