web-dev-qa-db-ja.com

宣言はstd名前空間に影響しますか?

#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

出力は-55になると予想していましたが、出力は-5-5です。

この事件はなぜ起こるのだろうか?

stdの使用とは何か関係がありますか?

94
Peter

言語仕様 許可 実装は<cmath>を実装し、global名前空間で標準関数を宣言(および定義)し、それらを名前空間stdに持ち込むusing宣言を使用します。このアプローチが使用されるかどうかは不明です

20.5.1.2ヘッダー
4[...]ただし、C++標準ライブラリでは、宣言(Cでマクロとして定義されている名前を除く) )は、名前空間stdの名前空間スコープ(6.3.6)内にあります。これらの名前(条項21から33およびAnnex Dで追加されたオーバーロードを含む)が最初にグローバル名前空間スコープ内で宣言され、次に明示的なusing宣言(10.3.3)によって名前空間stdに注入されるかどうかは指定されていません。

どうやら、このアプローチに従うことにした実装の1つ(GCCなど)を扱っているようです。つまり実装は::absを提供しますが、std::absは単に::absを「参照」します。

この場合に残る疑問の1つは、標準の::absに加えて、独自の::absを宣言できた理由、つまり多重定義エラーがない理由です。これは、いくつかの実装(GCCなど)によって提供される別の機能が原因である可能性があります。標準関数をいわゆる弱いシンボルとして宣言し、独自の定義で「置換」できるようにします。

これらの2つの要因が一緒になって、観察する効果を生み出します。::absの弱いシンボルの置換は、std::absの置換ももたらします。これが言語標準とどれだけよく一致するかは別の話です...いずれにしても、この動作に依存しないでください-言語によって保証されていません。

GCCでは、この動作を次の最小限の例で再現できます。 1つのソースファイル

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

別のソースファイル

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

この場合、2番目のソースファイルの::foo"Goodbye!")の新しい定義がN::fooの動作にも影響することもわかります。両方の呼び出しは"Goodbye!"を出力します。また、2番目のソースファイルから::fooの定義を削除すると、両方の呼び出しが::fooの「元の」定義にディスパッチされ、"Hello!"が出力されます。


上記の20.5.1.2/4によって与えられる許可は、<cmath>の実装を簡素化するためにあります。実装では、単にCスタイル<math.h>を含めてから、stdで関数を再宣言し、C++固有の追加および調整を追加できます。上記の説明が問題の内部メカニズムを適切に説明している場合、その大部分は、関数のCスタイルバージョンの弱いシンボルの置換可能性に依存しています。

上記のプログラムでintを単純にdoubleでグローバルに置き換える場合、コード(GCCの下)は「期待どおり」に動作し、-5 5を出力することに注意してください。これは、C標準ライブラリにabs(double)関数がないために発生します。独自のabs(double)を宣言することにより、何も置き換えません。

ただし、intからdoubleに切り替えた後、absからfabsに切り替えると、元の奇妙な動作が完全に再現されます(出力-5 -5)。

これは上記の説明と一致しています。

89
AnT

コードにより未定義の動作が発生します。

C++ 17 [extern.names]/4:

外部リンケージで宣言されたC標準ライブラリの各関数シグネチャは、extern "C"とextern "C++"リンケージの両方の関数シグネチャとして、またはグローバル名前空間の名前空間スコープの名前として使用するために実装に予約されます。

したがって、標準Cライブラリ関数int abs(int);と同じプロトタイプを持つ関数を作成することはできません。実際にどのヘッダーを含めるか、またはこれらのヘッダーがCライブラリ名をグローバル名前空間に入れるかどうかに関係なく。

ただし、異なるパラメータータイプを指定すると、absをオーバーロードすることができます。

13
M.M