11

演算子のオーバーロードのC++解決の奇妙な動作を見つけました、私は自分自身を説明することはできません。それを説明するいくつかのリソースへのポインタは、答えと同じくらい素晴らしいでしょう。

私は2つの翻訳ユニットを持っています。1つ(util.cpp / hと呼ばれる)では、2つの演算子を宣言して定義します(読みやすさのために実際の実装を省略します。とにかく問題が発生します)。

// util.h
#ifndef GUARD_UTIL
#define GUARD_UTIL

#include <iostream>

std::istream& operator>>(std::istream& is, const char* str);
std::istream& operator>>(std::istream& is, char* str);
#endif

と:

//util.cpp
#include "util.h"
#include <iostream>

std::istream& operator>>(std::istream& is, const char* str) {
  return is;  
}
std::istream& operator>>(std::istream& is, char* str) {
  return is;  
}

これらの演算子は、もちろんグローバル名前空間にある場合、std型と組み込み型で動作し、どこからでも使用できるはずです。これらは、グローバル名前空間(たとえば、main()から)から、またはグローバル名前空間にあることをコンパイラーに明示的に通知することで正常に機能します(コード例を参照)。

別の変換ユニット(test.cpp / hと呼ばれる)では、名前空間内でこれらの演算子を使用します。これは、この名前空間に同様の演算子を配置するまで機能します。この演算子が追加されるとすぐに、コンパイラー(gccやclangなど)は実行可能な演算子>>を見つけることができなくなります。

// test.h
#ifndef GUARD_TEST
#define GUARD_TEST

#include <iostream>

namespace Namespace {
  class SomeClass {   
    public:
      void test(std::istream& is);
  };

  // without the following line everything compiles just fine
  std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
}

#endif

と:

//test.cpp
#include "test.h"
#include "util.h"
#include <iostream>

void Namespace::SomeClass::test(std::istream& is) {
  ::operator>>(is, "c"); //works
  is >> "c" //fails
}

名前空間に演算子>>がないのに、コンパイラが正しい演算子を検出するのに、存在する場合は検出できないのはなぜですか?署名が異なっていても、演算子が正しいものを見つけるコンパイラの機能に影響を与えるのはなぜですか?

これを修正する1つの試みは、

std :: istream&operator >>(std :: istream&is、const char * str){:: operator >>(is、str); }

名前空間に入れますが、リンカーが以前の定義について文句を言うよりも。だから追加:なぜリンカーはコンパイラーが見つけられないものを見つけることができるのですか?

4

3 に答える 3

10

これは名前の隠蔽の問題です。標準は言う(c ++ 03、3.3.7 / 1)

ネストされた宣言領域または派生クラス (10.2) で同じ名前を明示的に宣言することにより、名前を非表示にすることができます。

あなたの場合の「名前」は でoperator>>あり、名前空間はネストされた宣言領域を構成します。

これを修正する最も簡単な方法はusing、 namespace-local を宣言する宣言を使用することoperator<<です。

namespace your_namespece {
    std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
    using ::operator>>;
}

この機能は Koenig ルックアップに干渉しないことに注意してください (少なくともあなたのケースでは、原則として可能です) std::

PS: この問題を回避する別の可能性は、演算子 forSomeClassinlinefriendとして定義することです。このような関数は名前空間レベル (「その」クラスの外) で宣言されますが、そこからは見えません。これらは、Koenig ルックアップによってのみ見つけることができます。

于 2012-07-06T09:41:39.427 に答える
3

ここにはいくつかの問題があります。手始めに、すでに存在するグローバル名前空間の関数を再定義していますstd::. ただし、あなたが説明する問題は、名前検索の仕組みによるものです。基本的に、演算子のオーバーロードの場合、コンパイラは 2 つの名前検索を行います。最初の (演算子だけでなく、すべてのシンボルに使用される) は、シンボルが表示されるスコープから開始し、外側に向かって機能します: 最初にローカル ブロック、次にクラスとその基底クラス (存在する場合)、最後に名前空間が機能します。グローバル名前空間に。この検索の重要な特徴は、名前が見つかったスコープで停止することです。ローカル スコープで名前が見つかった場合、どのクラスも検索しません。クラスで見つかった場合は、基本クラスまたは名前空間を検索しません。また、名前空間で見つかった場合は、それを囲む名前空間を検索しません。このルックアップに関する限り、すべてのオーバーロードは同じスコープ内にある必要があります。2 番目のルックアップは、関数と演算子のオーバーロードにのみ影響し、引数として使用されるクラスまたはオブジェクトのコンテキストで発生します。したがって、オペランドの 1 つが標準ライブラリのクラス (または標準ライブラリのクラスから派生したもの) である場合、コンパイラは次の関数を探します。std::、シンボルが使用されているコンテキストには含まれていませんがstd::。あなたが抱えている問題は、 のような組み込み型が名前空間を意味char*ないことです(グローバルでさえありませ ) 。あなたの機能はどちらにもありません。オーバーロードされた演算子を見つけたい場合は、そのオペランドの 1 つのスコープでそれを定義する必要があります。operator>>std::

std::istream& operator>>( std::istream&, char* )具体的には、ここでは、標準ライブラリで既にオーバーロードされているため、 オーバーロードできません。std::istream& operator>>( std::istream&, char const* )可能ですが、2番目のオペランドに書き込むことができないため、何をすべきかわかりません。より一般的には、定義した型に対してのみこの演算子をオーバーロードする必要があり、オーバーロードを型自体と同じ名前空間に配置して、上記の 2 番目のルックアップ (Argument Dependent Lookup、または ADL と呼ばれる) で検出されるようにする必要があります。 —またはそれ以前は、それを発明した人物にちなんで、ケーニッヒ ルックアップ)。

于 2012-07-06T10:38:14.073 に答える
0

::はグローバルスコープであるため、コンパイラはグローバル名前空間をスキャンしてこの演算子を見つける必要があります。は>>"C"であり、名前空間で演算子>>を見つけようとします。したがって、コンパイラはそれを見つけて検索を停止し、そのような演算子がない場合、コンパイラは必要な署名を持つ演算子を選択しようとします-コンパイラは失敗します。ハーブサッターエクセプショナルC++をお読みください。

于 2012-07-06T09:40:36.963 に答える