宣言はstd名前空間に影響を与えることができますか?


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

出力は-5and 5になると思っていましたが、出力は-5and -5です。

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

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


1
の実装absが正しくありません。
Richard Critten

31
@RichardCrittenそれがポイントです。OPは、これを追加することがなぜabs影響するかを尋ねていstd::abs()ます。
HolyBlackCat

11
面白い、私が手55打ち鳴らすと、-5そして-5gccで。
Rakete1111

10
Cmakeはコンパイラではなく、ビルドシステムです。cmakeを使用して、さまざまなコンパイラーでビルドできます。
HolyBlackCat

5
おそらく、単に関数を用意することをお勧めします。return 0これにより、誤って関数を誤って実装したと人々が考えて、望ましい実際の動作が明確になるのを防ぐことができます。
Bernhard Barker

回答:


92

言語仕様で<cmath>グローバル名前空間で標準関数を宣言(および定義)し、stdusing-declarationsを使用してそれらを名前空間に組み込むことで、実装を実装できます。このアプローチが使用されるかどうかは指定されていません

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

どうやら、このアプローチ(GCCなど)に従うことを決定した実装の1つを扱っています。つまり、実装はを提供しますが::absstd::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!"。また::foo、2番目のソースファイルからの定義を削除すると、両方の呼び出しがと「::foo出力」の「元の」定義にディスパッチされます"Hello!"


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

我々は単に世界的に交換する場合に留意intしてdouble上記のプログラムでは「予想通り」、(GCC下)のコードは動作します-それは、出力は以下となります-5 5。これは、C標準ライブラリにabs(double)機能がないために発生します。独自のを宣言するabs(double)ことで、何も置き換えません。

しかしから切り替えた後場合intdouble、我々はまた、から切り替えるabsfabsは、元の奇妙な振る舞いは、その咲き誇る(出力に再表示されます-5 -5)。

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


cmathのソースで見ることができるusing ::abs;ように、using ::asin;そのようなものはないので、宣言をオーバーライドできます。もう1つの注意点は、std名前空間関数で定義されているのは、intではなくdoublefloat
Take_Care_

2
標準の観点から、動作は[extern.names] / 4ごとに定義されていません。
xskxzr

私は削除する場合でも、#include<cmath>私のコードでは、私は同じanswer.`だ
ピーター・

@ピーターしかし、あなたはどこでstd :: absを取得していますか?-別のインクルードを介してインクルードされている可能性があります。その時点で、この説明に戻ります。(ヘッダーが直接または間接的に含まれているかどうかはコンパイラーにとって重要ではありません。)
RM

@Peter:absでも宣言できます。<cstdlib>これは、を介して暗黙的にインクルードされる場合があります<iostream>。自分のものabsを削除して、それでもコンパイルできるかどうかを確認してください。
AnT

13

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

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

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

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

ただし、abs異なるパラメータータイプを指定すると、オーバーロードが許可されます。


1
「またはグローバルネームスペースのネームスペーススコープの名前として」、グローバルネームスペースでオーバーロードできません。
xskxzr

@xskxzr引用したテキストの解釈についてはわかりません。ユーザーがグローバル名前空間でその名前を宣言できないことを意味する場合、[extern.names] / 3と同様に、引用したテキストの前の部分は冗長になります。ここで何か他のものが意図されていたと思います。
MM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.