1. ホーム
  2. c++

[解決済み] ある宣言がstd名前空間に影響を与えることは可能か?

2023-02-03 16:36:31

質問

#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;
}

という出力になると思っていました。 -5 であり 5 が、出力されるのは -5-5 .

なぜこのようなケースが起こるのでしょうね。

の使用と関係があるのでしょうか? std の使用と関係があるのでしょうか?

どのように解決するのですか?

言語仕様 では を実装することができます。 <cmath> の標準的な関数を宣言(定義)することにより グローバル 名前空間で宣言し、それを名前空間 std をusing-declarationsによって呼び出す。この方法が使われるかどうかは未定です

20.5.1.2 ヘッダ

4 [...] しかし、C++標準ライブラリでは、宣言は(Cでマクロとして定義されている名前を除いて)名前空間のスコープ内(6.3.6)にある std . これらの名前(第21項から第33項までに追加されたオーバーロードを含む)が、C言語の名前空間の範囲(6.3.6)内にあるかどうかは特定されていません。 がグローバルな名前空間スコープで最初に宣言され,その後,名前空間 std に注入されるかどうかは特定されていません。

どうやら、このアプローチに従うことを決めた実装の一つを扱っているようです(例えば、GCCなど)。つまり、あなたの実装では ::abs を提供し、一方 std::abs は単に ::abs .

この場合、一つ疑問が残るのは、なぜ標準の ::abs を宣言することができたのかということです。 ::abs を宣言できたはずです。つまり、多重定義エラーは発生しません。これは、いくつかの実装(例えばGCC)が提供する別の機能によって引き起こされるかもしれません: 彼らは、標準的な関数をいわゆる 弱いシンボル このため、独自の定義でそれらを "replace"することができます。

これら 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();
}

この場合、新しい定義の ::foo ( "Goodbye!" ) の挙動にも影響を与えます。 N::foo . どちらの呼び出しも "Goodbye!" . そして、もし ::foo の定義を削除すると、両方の呼び出しが "original" の定義にディスパッチされます。 ::foo の定義にディスパッチし、出力します。 "Hello!" .


上記の20.5.1.2/4で与えられている許可は <cmath> . 実装は、単にC言語スタイルの <math.h> で関数を再宣言します。 std で関数を再定義し、C++特有の追加や調整を加えてください。もし上記の説明がこの問題の内部構造を適切に説明しているならば、その主要な部分は C スタイルのバージョン の弱点記号の置換可能性に依存します。

単にグローバルに置き換えるなら intdouble を指定すると、(GCCの下で)コードは期待通りに動作し、次のように出力します。 -5 5 . これは、Cの標準ライブラリには abs(double) 関数がないためです。独自の abs(double) を宣言することで、何も置き換えることはありません。

しかし、もし int から double から、さらに abs から fabs に変更すると、元の奇妙な動作が完全に再現されます(出力は -5 -5 ).

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